Compare commits

..

18 Commits

Author SHA1 Message Date
JustinKol
1858c04629 Update .npmrc 2025-05-20 08:17:54 -04:00
Justin Kolasa (from Dev Box)
2db9978d72 Changed the VSCode detection to a test url that will not open if successful 2025-05-20 08:02:13 -04:00
Justin Kolasa (from Dev Box)
ec195c466a Merge branch 'users/justinkolasa/VSCode-improvements' of https://github.com/Azure/cosmos-explorer into users/justinkolasa/VSCode-improvements 2025-05-19 18:20:59 -04:00
Justin Kolasa (from Dev Box)
3f3c3f1b83 Fixed ESLint error 2025-05-19 16:32:47 -04:00
JustinKol
2318061ceb Update settings.json 2025-05-19 16:27:21 -04:00
JustinKol
fd5b9a92ca Update .npmrc 2025-05-19 16:27:03 -04:00
Justin Kolasa (from Dev Box)
b24b3d6b8e Prettier run 2025-05-19 16:24:33 -04:00
Justin Kolasa (from Dev Box)
0ccedfb9bd Better VSCode detection 2025-05-19 16:22:24 -04:00
Justin Kolasa (from Dev Box)
97eb4fe3ef Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2025-05-19 16:14:35 -04:00
Justin Kolasa (from Dev Box)
f1484a72c8 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2025-05-15 16:56:07 -04:00
Justin Kolasa (from Dev Box)
4444f66e91 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2025-05-14 10:02:56 -04:00
Justin Kolasa (from Dev Box)
2180eba0a0 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2025-05-14 09:47:45 -04:00
Justin Kolasa (from Dev Box)
ea1f0e9b0c Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2025-05-13 11:37:09 -04:00
Justin Kolasa (from Dev Box)
f66b78aaf8 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2025-05-13 09:28:03 -04:00
Justin Kolasa (from Dev Box)
30182ed503 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2025-05-08 08:55:42 -04:00
Justin Kolasa (from Dev Box)
9bf8cffd29 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2025-05-05 11:29:18 -04:00
Justin Kolasa (from Dev Box)
cb92bdb003 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2025-05-02 11:22:49 -04:00
Justin Kolasa (from Dev Box)
5f4ea0e614 master pull 2025-04-22 08:50:29 -04:00
43 changed files with 1048 additions and 3182 deletions

2
.npmrc
View File

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

View File

@@ -24,8 +24,5 @@
"source.organizeImports": "explicit"
},
"typescript.preferences.importModuleSpecifier": "non-relative",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescriptreact]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
}
"editor.defaultFormatter": "esbenp.prettier-vscode"
}

211
package-lock.json generated
View File

@@ -10,7 +10,7 @@
"hasInstallScript": true,
"dependencies": {
"@azure/arm-cosmosdb": "9.1.0",
"@azure/cosmos": "4.3.0",
"@azure/cosmos": "4.2.0-beta.1",
"@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "4.5.0",
"@azure/msal-browser": "2.14.2",
@@ -290,71 +290,59 @@
"version": "2.6.2",
"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": {
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.20.0.tgz",
"integrity": "sha512-ASoP8uqZBS3H/8N8at/XwFr6vYrRP3syTK0EUjDXQy0Y1/AUS+QeIRThKmTNJO2RggvBBxaXDPM7YoIwDGeA0g==",
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.18.0.tgz",
"integrity": "sha512-QSoGUp4Eq/gohEFNJaUOwTN7BCc2nHTjjbm75JT0aD7W65PWM1H/tItz0GsABn22uaKyGxiMhWQLt2r+FGU89Q==",
"dependencies": {
"@azure/abort-controller": "^2.0.0",
"@azure/core-auth": "^1.8.0",
"@azure/core-tracing": "^1.0.1",
"@azure/core-util": "^1.11.0",
"@azure/logger": "^1.0.0",
"@typespec/ts-http-runtime": "^0.2.2",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.0",
"tslib": "^2.6.2"
},
"engines": {
"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": {
"version": "2.6.2",
"license": "0BSD"
@@ -391,16 +379,15 @@
"license": "0BSD"
},
"node_modules/@azure/cosmos": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-4.3.0.tgz",
"integrity": "sha512-0Ls3l1uWBBSphx6YRhnM+w7rSvq8qVugBCdO6kSiNuRYXEf6+YWLjbzz4e7L2kkz/6ScFdZIOJYP+XtkiRYOhA==",
"version": "4.2.0-beta.1",
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-4.2.0-beta.1.tgz",
"integrity": "sha512-mREONehm1DxjEKXGaNU6Wmpf9Ckb9IrhKFXhDFVs45pxmoEb3y2s/Ub0owuFmqlphpcS1zgtYQn5exn+lwnJuQ==",
"dependencies": {
"@azure/abort-controller": "^2.0.0",
"@azure/core-auth": "^1.7.1",
"@azure/core-rest-pipeline": "^1.15.1",
"@azure/core-tracing": "^1.1.1",
"@azure/core-util": "^1.8.1",
"@azure/keyvault-keys": "^4.8.0",
"fast-json-stable-stringify": "^2.1.0",
"jsbi": "^4.3.0",
"priorityqueuejs": "^2.0.0",
@@ -505,66 +492,14 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"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": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.2.0.tgz",
"integrity": "sha512-0hKEzLhpw+ZTAfNJyRrn6s+V0nDWzXk9OjBr2TiGIu0OfMr5s2V4FpKLTAK3Ca5r5OKLbf4hkOGDPyiRjie/jA==",
"version": "1.0.4",
"license": "MIT",
"dependencies": {
"@typespec/ts-http-runtime": "^0.2.2",
"tslib": "^2.6.2"
"tslib": "^2.2.0"
},
"engines": {
"node": ">=18.0.0"
"node": ">=14.0.0"
}
},
"node_modules/@azure/logger/node_modules/tslib": {
@@ -13139,56 +13074,6 @@
"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": {
"version": "0.2.2",
"license": "ISC"

View File

@@ -5,7 +5,7 @@
"main": "index.js",
"dependencies": {
"@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/identity": "4.5.0",
"@azure/msal-browser": "2.14.2",

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
import { monaco } from "Explorer/LazyMonaco";
import { getRUThreshold, ruThresholdEnabled } from "Shared/StorageUtility";
export enum QueryErrorSeverity {
Error = "Error",
@@ -102,9 +103,20 @@ export interface ErrorEnrichment {
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 {
message: string;

View File

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

View File

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

View File

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

View File

@@ -50,7 +50,6 @@ export interface QueryResults extends QueryResultsMetadata {
roundTrips?: number;
headers?: any;
queryMetrics?: QueryMetrics;
ruThresholdExceeded?: boolean;
}
export interface Button {

View File

@@ -13,7 +13,7 @@ import {
} from "Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent";
import { useDatabases } from "Explorer/useDatabases";
import { isFabricNative } from "Platform/Fabric/FabricUtil";
import { isVectorSearchEnabled } from "Utils/CapabilityUtils";
import { isFullTextSearchEnabled, isVectorSearchEnabled } from "Utils/CapabilityUtils";
import { isRunningOnPublicCloud } from "Utils/CloudUtils";
import * as React from "react";
import DiscardIcon from "../../../../images/discard.svg";
@@ -188,7 +188,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.isGlobalSecondaryIndex =
!!this.collection?.materializedViewDefinition() || !!this.collection?.materializedViews();
this.isVectorSearchEnabled = isVectorSearchEnabled() && !hasDatabaseSharedThroughput(this.collection);
this.isFullTextSearchEnabled = userContext.apiType === "SQL";
this.isFullTextSearchEnabled = isFullTextSearchEnabled() && !hasDatabaseSharedThroughput(this.collection);
this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy;
this.throughputBucketsEnabled = userContext.throughputBucketsEnabled;
@@ -1091,7 +1091,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
currentOffer: this.collection.offer(),
autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined,
manualThroughput: this.state.isAutoPilotSelected ? undefined : this.state.throughput,
throughputBuckets: this.throughputBucketsEnabled ? this.state.throughputBuckets : undefined,
};
if (this.hasProvisioningTypeChanged()) {
if (this.state.isAutoPilotSelected) {

View File

@@ -198,32 +198,6 @@ exports[`SettingsComponent renders 1`] = `
timeToLiveSecondsBaseline={5}
/>
</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
headerText="Indexing Policy"
itemKey="IndexingPolicyTab"

View File

@@ -1,4 +1,4 @@
import { Stack, Text } from "@fluentui/react";
import { Text } from "@fluentui/react";
import React, { FunctionComponent } from "react";
import { InfoTooltip } from "../../../../Common/Tooltip/InfoTooltip";
import * as SharedConstants from "../../../../Shared/Constants";
@@ -54,32 +54,28 @@ export const CostEstimateText: FunctionComponent<CostEstimateTextProps> = ({
if (isAutoscale) {
return (
<Stack style={{ marginBottom: 6 }}>
<Text variant="small">
{estimatedMonthlyCost} ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
<b>
{currencySign + calculateEstimateNumber(monthlyPrice / 10)} -{" "}
{currencySign + calculateEstimateNumber(monthlyPrice)}{" "}
</b>
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits / 10} - {requestUnits}{" "}
RU/s, {currencySign + pricePerRu}/RU)
</Text>
</Stack>
<Text variant="small">
{estimatedMonthlyCost} ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
<b>
{currencySign + calculateEstimateNumber(monthlyPrice / 10)} -{" "}
{currencySign + calculateEstimateNumber(monthlyPrice)}{" "}
</b>
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits / 10} - {requestUnits}{" "}
RU/s, {currencySign + pricePerRu}/RU)
</Text>
);
}
return (
<Stack style={{ marginBottom: 8 }}>
<Text variant="small">
Estimated cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
<b>
{currencySign + calculateEstimateNumber(hourlyPrice)} hourly /{" "}
{currencySign + calculateEstimateNumber(dailyPrice)} daily /{" "}
{currencySign + calculateEstimateNumber(monthlyPrice)} monthly{" "}
</b>
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits}RU/s,{" "}
{currencySign + pricePerRu}/RU)
</Text>
</Stack>
<Text variant="small">
Estimated cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
<b>
{currencySign + calculateEstimateNumber(hourlyPrice)} hourly /{" "}
{currencySign + calculateEstimateNumber(dailyPrice)} daily /{" "}
{currencySign + calculateEstimateNumber(monthlyPrice)} monthly{" "}
</b>
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits}RU/s,{" "}
{currencySign + pricePerRu}/RU)
</Text>
);
};

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 { CostEstimateText } from "Explorer/Controls/ThroughputInput/CostEstimateText/CostEstimateText";
import { useDatabases } from "Explorer/useDatabases";
import React, { FunctionComponent, useEffect, useState } from "react";
import * as Constants from "../../../Common/Constants";
@@ -10,8 +9,8 @@ import { userContext } from "../../../UserContext";
import { getCollectionName } from "../../../Utils/APITypeUtils";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import * as PricingUtils from "../../../Utils/PricingUtils";
import { CostEstimateText } from "./CostEstimateText/CostEstimateText";
import "./ThroughputInput.less";
import { isFabricNative } from "../../../Platform/Fabric/FabricUtil";
export interface ThroughputInputProps {
isDatabase: boolean;
@@ -44,8 +43,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
if (
isFreeTier ||
isQuickstart ||
[Constants.WorkloadType.Learning, Constants.WorkloadType.DevelopmentTesting].includes(workloadType) ||
isFabricNative()
[Constants.WorkloadType.Learning, Constants.WorkloadType.DevelopmentTesting].includes(workloadType)
) {
defaultThroughput = AutoPilotUtils.autoPilotThroughput1K;
} else if (workloadType === Constants.WorkloadType.Production) {
@@ -232,92 +230,53 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
</div>
</Stack>
)}
{isAutoscaleSelected && (
<Stack className="throughputInputSpacing">
<Text style={{ marginTop: -2, fontSize: 12 }}>
Your container throughput will automatically scale up to the maximum value you select, from a minimum of 10%
of that value.
</Text>
<Stack horizontal verticalAlign="end" tokens={{ childrenGap: 8 }}>
<Stack tokens={{ childrenGap: 4 }}>
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 4 }}>
<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,
}}
<Text variant="small" aria-label="capacity calculator of azure cosmos db">
Estimate your required RU/s with{" "}
<Link
className="underlinedLink outlineNone"
target="_blank"
href="https://cosmos.azure.com/capacitycalculator/"
aria-label="capacity calculator of azure cosmos db"
>
x 10 =
</Text>
capacity calculator
</Link>
.
</Text>
<Stack tokens={{ childrenGap: 4 }}>
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 4 }}>
<Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }}>
Maximum RU/s
</Text>
<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 horizontal>
<Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }} aria-label="maxRUDescription">
{isDatabase ? "Database" : getCollectionName()} Max RU/s
</Text>
<InfoTooltip>{getAutoScaleTooltip()}</InfoTooltip>
</Stack>
<CostEstimateText requestUnits={throughput} isAutoscale={isAutoscaleSelected} />
<Stack className="throughputInputSpacing">
<Text variant="small" aria-label="ruDescription">
Estimate your required RU/s with&nbsp;
<Link
className="underlinedLink"
target="_blank"
href="https://cosmos.azure.com/capacitycalculator/"
aria-label="Capacity calculator"
>
capacity calculator
</Link>
.
</Text>
</Stack>
<Separator className="panelSeparator" style={{ paddingTop: -8, paddingBottom: -8 }} />
<TextField
id="autoscaleRUValueField"
type="number"
styles={{
fieldGroup: { width: 300, height: 27 },
field: { fontSize: 12 },
}}
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}
/>
<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>
)}
@@ -341,6 +300,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
</Text>
<InfoTooltip>{getAutoScaleTooltip()}</InfoTooltip>
</Stack>
<TooltipHost
directionalHint={DirectionalHint.topLeftEdge}
content={
@@ -365,10 +325,11 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
errorMessage={throughputError}
/>
</TooltipHost>
<CostEstimateText requestUnits={throughput} isAutoscale={isAutoscaleSelected} />
</Stack>
)}
<CostEstimateText requestUnits={throughput} isAutoscale={isAutoscaleSelected} />
{throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && (
<Stack horizontal verticalAlign="start">
<Checkbox

View File

@@ -292,32 +292,87 @@ export default class Explorer {
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",
// Detect if VS Code is installed
const detectVSCode = () => {
return new Promise<boolean>((resolve) => {
// Create hidden iframe to detect protocol handler
const iframe = document.createElement("iframe");
iframe.style.display = "none";
document.body.appendChild(iframe);
onPrimaryButtonClick: () => {
const timeoutId = setTimeout(() => {
if (document.body.contains(iframe)) {
document.body.removeChild(iframe);
}
resolve(false);
}, 1000);
try {
// Listen for blur event which might indicate app was launched
const onBlur = () => {
clearTimeout(timeoutId);
window.removeEventListener("blur", onBlur);
if (document.body.contains(iframe)) {
document.body.removeChild(iframe);
}
// Window lost focus, likely because VS Code opened
resolve(true);
};
window.addEventListener("blur", onBlur, { once: true });
// Use a test URL that won't open a specific resource but will check if the extension is installed
iframe.src = "vscode://ms-azuretools.vscode-cosmosdb?test=1";
} catch (error) {
clearTimeout(timeoutId);
if (document.body.contains(iframe)) {
document.body.removeChild(iframe);
}
resolve(false);
}
});
};
detectVSCode().then((isVSCodeInstalled) => {
if (isVSCodeInstalled) {
try {
window.location.href = vscodeUrl;
TelemetryProcessor.traceStart(Action.OpenVSCode);
} catch (error) {
logConsoleError(`Failed to open VS Code: ${getErrorMessage(error)}`);
showVSCodeDialog();
}
},
onSecondaryButtonClick: () => {
useDialog.getState().closeDialog();
TelemetryProcessor.traceCancel(Action.OpenVSCode);
},
} else {
showVSCodeDialog();
}
});
const showVSCodeDialog = () => {
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);
};
useDialog.getState().openDialog(openVSCodeDialogProps);
}
public async openCESCVAFeedbackBlade(): Promise<void> {

View File

@@ -16,12 +16,7 @@ import * as StorageUtility from "../../../Shared/StorageUtility";
import { LocalStorageUtility, StorageKey } from "../../../Shared/StorageUtility";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import {
logConsoleError,
logConsoleInfo,
logConsoleProgress,
logConsoleWarning,
} from "../../../Utils/NotificationConsoleUtils";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
import { EditorReact } from "../../Controls/Editor/EditorReact";
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
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.Info, 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) {
let errorDataStr = "";
if (errorData && errorData.length > 0) {
@@ -1105,8 +1099,6 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
return logConsoleInfo(consoleMessage);
case ConsoleDataType.InProgress:
return logConsoleProgress(consoleMessage);
case ConsoleDataType.Warning:
return logConsoleWarning(consoleMessage);
}
}

View File

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

View File

@@ -173,20 +173,8 @@
.message {
flex-grow: 1;
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 InfoIcon from "../../../../images/info_color.svg";
import LoadingIcon from "../../../../images/loading.svg";
import WarningIcon from "../../../../images/warning.svg";
import { ClientDefaults, KeyCodes } from "../../../Common/Constants";
import { userContext } from "../../../UserContext";
import { useNotificationConsole } from "../../../hooks/useNotificationConsole";
@@ -92,9 +91,6 @@ export class NotificationConsoleComponent extends React.Component<
const numInfoItems = this.state.allConsoleData.filter(
(data: ConsoleData) => data.type === ConsoleDataType.Info,
).length;
const numWarningItems = this.state.allConsoleData.filter(
(data: ConsoleData) => data.type === ConsoleDataType.Warning,
).length;
return (
<div className="notificationConsoleContainer">
@@ -122,10 +118,6 @@ export class NotificationConsoleComponent extends React.Component<
<img src={infoBubbleIcon} alt="Info items" />
<span className="numInfoItems">{numInfoItems}</span>
</span>
<span className="notificationConsoleHeaderIconWithData">
<img src={WarningIcon} alt="Warning items" />
<span className="numWarningItems">{numWarningItems}</span>
</span>
</span>
{userContext.features.pr && <PrPreview pr={userContext.features.pr} />}
<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.Error && <img className="errorIcon" src={ErrorRedIcon} alt="error" />}
{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="message" role="alert" aria-live="assertive">
{item.message}

View File

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

View File

@@ -25,7 +25,7 @@ import { FullTextPoliciesComponent } from "Explorer/Controls/FullTextSeach/FullT
import { VectorEmbeddingPoliciesComponent } from "Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent";
import {
AllPropertiesIndexed,
AnalyticalStoreHeader,
AnalyticalStorageContent,
ContainerVectorPolicyTooltipContent,
FullTextPolicyDefault,
getPartitionKey,
@@ -49,7 +49,12 @@ import { Action } from "Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
import { userContext } from "UserContext";
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 { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils";
import { CollapsibleSectionComponent } from "../../Controls/CollapsiblePanel/CollapsibleSectionComponent";
@@ -60,7 +65,6 @@ import { useDatabases } from "../../useDatabases";
import { PanelFooterComponent } from "../PanelFooterComponent";
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
import { PanelLoadingScreen } from "../PanelLoadingScreen";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
export interface AddCollectionPanelProps {
explorer: Explorer;
@@ -106,7 +110,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
private collectionThroughput: number;
private isCollectionAutoscale: boolean;
private isCostAcknowledged: boolean;
private showFullTextSearch: boolean;
constructor(props: AddCollectionPanelProps) {
super(props);
@@ -141,8 +144,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
fullTextIndexes: [],
fullTextPolicyValidated: true,
};
this.showFullTextSearch = userContext.apiType === "SQL";
}
componentDidMount(): void {
@@ -265,7 +266,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
<div className="panelMainContent">
{!(isFabricNative() && this.props.databaseId !== undefined) && (
<Stack hidden={userContext.apiType === "Tables"} style={{ marginBottom: -2 }}>
<Stack hidden={userContext.apiType === "Tables"}>
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
@@ -407,12 +408,12 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
responsiveMode={999}
/>
)}
<Separator className="panelSeparator" />
</Stack>
)}
<Separator className="panelSeparator" style={{ marginTop: -4, marginBottom: -4 }} />
<Stack>
<Stack horizontal style={{ marginTop: -5, marginBottom: 1 }}>
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
{`${getCollectionName()} id`}
@@ -450,10 +451,10 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
}
/>
</Stack>
<Separator className="panelSeparator" style={{ marginTop: -5, marginBottom: -5 }} />
{this.shouldShowIndexingOptionsForFreeTierAccount() && (
<Stack>
<Stack horizontal style={{ marginTop: -4, marginBottom: -5 }}>
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
Indexing
@@ -499,7 +500,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
(!this.state.isSharedThroughputChecked ||
this.props.explorer.isFixedCollectionWithSharedThroughputSupported()) && (
<Stack>
<Stack horizontal style={{ marginTop: -5, marginBottom: -4 }}>
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
Sharding
@@ -555,7 +556,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
{this.state.isSharded && (
<Stack>
<Stack horizontal style={{ marginTop: -5, marginBottom: -4 }}>
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
{getPartitionKeyName()}
@@ -599,7 +600,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
{userContext.apiType === "SQL" &&
this.state.subPartitionKeys.map((subPartitionKey: string, index: number) => {
return (
<Stack style={{ marginBottom: 2, marginTop: -5 }} key={`uniqueKey${index}`} horizontal>
<Stack style={{ marginBottom: 8 }} key={`uniqueKey${index}`} horizontal>
<div
style={{
width: "20px",
@@ -667,7 +668,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
)}
</Stack>
)}
<Separator className="panelSeparator" style={{ marginTop: 2, marginBottom: -4 }} />
</Stack>
)}
@@ -728,7 +728,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
)}
{!isFabricNative() && userContext.apiType === "SQL" && (
<Stack style={{ marginTop: -2, marginBottom: -4 }}>
<Stack>
{UniqueKeysHeader()}
{this.state.uniqueKeys.map((uniqueKey: string, i: number): JSX.Element => {
return (
@@ -777,12 +777,10 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</Stack>
)}
<Separator className="panelSeparator" style={{ marginTop: -15, marginBottom: -4 }} />
{shouldShowAnalyticalStoreOptions() && (
<Stack className="panelGroupSpacing" style={{ marginTop: -4 }}>
<Stack className="panelGroupSpacing">
<Text className="panelTextBold" variant="small">
{AnalyticalStoreHeader()}
{AnalyticalStorageContent()}
</Text>
<Stack horizontal verticalAlign="center">
@@ -823,7 +821,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
<Stack className="panelGroupSpacing">
<Text variant="small">
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
href="https://aka.ms/cosmosdb-synapselink"
target="_blank"
@@ -1163,7 +1161,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
}
private shouldShowFullTextSearchParameters() {
return !isFabricNative() && this.showFullTextSearch;
return isFullTextSearchEnabled() && (isServerlessAccount() || this.shouldShowCollectionThroughputInput());
}
private parseUniqueKeys(): DataModels.UniqueKeyPolicy {
@@ -1318,7 +1316,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
};
}
if (this.showFullTextSearch) {
if (this.shouldShowFullTextSearchParameters()) {
indexingPolicy.fullTextIndexes = this.state.fullTextIndexes;
}
@@ -1352,12 +1350,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
let offerThroughput: number;
let autoPilotMaxThroughput: number;
// Throughput
if (isFabricNative()) {
// Fabric Native accounts are always autoscale and have a fixed throughput of 1K
autoPilotMaxThroughput = AutoPilotUtils.autoPilotThroughput1K;
offerThroughput = undefined;
} else if (databaseLevelThroughput) {
if (databaseLevelThroughput) {
if (this.state.createNewDatabase) {
if (this.isNewDatabaseAutoscale) {
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.";
return (
<Stack horizontal style={{ marginBottom: -2 }}>
<Stack horizontal>
<Text className="panelTextBold" variant="small">
Unique keys
</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 {
return (
<Text variant="small">

View File

@@ -11,11 +11,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
>
<Stack
hidden={false}
style={
{
"marginBottom": -2,
}
}
>
<Stack
horizontal={true}
@@ -143,25 +138,13 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
</StyledTooltipHostBase>
</Stack>
</Stack>
<Separator
className="panelSeparator"
/>
</Stack>
<Separator
className="panelSeparator"
style={
{
"marginBottom": -4,
"marginTop": -4,
}
}
/>
<Stack>
<Stack
horizontal={true}
style={
{
"marginBottom": 1,
"marginTop": -5,
}
}
>
<span
className="mandatoryStar"
@@ -204,24 +187,9 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
value=""
/>
</Stack>
<Separator
className="panelSeparator"
style={
{
"marginBottom": -5,
"marginTop": -5,
}
}
/>
<Stack>
<Stack
horizontal={true}
style={
{
"marginBottom": -4,
"marginTop": -5,
}
}
>
<span
className="mandatoryStar"
@@ -286,15 +254,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
Add hierarchical partition key
</CustomizedDefaultButton>
</Stack>
<Separator
className="panelSeparator"
style={
{
"marginBottom": -4,
"marginTop": 2,
}
}
/>
</Stack>
<ThroughputInput
isDatabase={false}
@@ -304,21 +263,9 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
setIsThroughputCapExceeded={[Function]}
setThroughputValue={[Function]}
/>
<Stack
style={
{
"marginBottom": -4,
"marginTop": -2,
}
}
>
<Stack>
<Stack
horizontal={true}
style={
{
"marginBottom": -2,
}
}
>
<Text
className="panelTextBold"
@@ -359,53 +306,26 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
Add unique key
</CustomizedActionButton>
</Stack>
<Separator
className="panelSeparator"
style={
{
"marginBottom": -4,
"marginTop": -15,
}
}
/>
<Stack
className="panelGroupSpacing"
style={
{
"marginTop": -4,
}
}
>
<Text
className="panelTextBold"
variant="small"
>
<Stack
horizontal={true}
style={
{
"marginBottom": -2,
}
}
<Text
variant="small"
>
<Text
className="panelTextBold"
variant="small"
Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads.
<StyledLinkBase
aria-label="Learn more about analytical store."
href="https://aka.ms/analytical-store-overview"
target="_blank"
>
Analytical Store
</Text>
<StyledTooltipHostBase
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>
Learn more
</StyledLinkBase>
</Text>
</Text>
<Stack
horizontal={true}
@@ -461,8 +381,8 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
Azure Synapse Link is required for creating an analytical store
container
. Enable Synapse Link for this Cosmos DB account.
<br />
. Enable Synapse Link for this Cosmos DB account.
<StyledLinkBase
aria-label="Learn more about Azure Synapse Link."
className="capacitycalculator-link"
@@ -491,44 +411,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
/>
</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
isExpandedByDefault={false}
onExpand={[Function]}

View File

@@ -40,12 +40,12 @@ import { PanelInfoErrorComponent } from "Explorer/Panes/PanelInfoErrorComponent"
import { PanelLoadingScreen } from "Explorer/Panes/PanelLoadingScreen";
import { useDatabases } from "Explorer/useDatabases";
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 { Action } from "Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
import { userContext } from "UserContext";
import { isServerlessAccount, isVectorSearchEnabled } from "Utils/CapabilityUtils";
import { isFullTextSearchEnabled, isServerlessAccount, isVectorSearchEnabled } from "Utils/CapabilityUtils";
import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils";
export interface AddGlobalSecondaryIndexPanelProps {
@@ -75,8 +75,6 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
const [showErrorDetails, setShowErrorDetails] = useState<boolean>();
const [isExecuting, setIsExecuting] = useState<boolean>();
const showFullTextSearch: MutableRefObject<boolean> = useRef<boolean>(userContext.apiType === "SQL");
useEffect(() => {
const sourceContainerOptions: IDropdownOption[] = [];
useDatabases.getState().databases.forEach((database: Database) => {
@@ -142,6 +140,10 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
return isVectorSearchEnabled() && (isServerlessAccount() || showCollectionThroughputInput());
};
const showFullTextSearchParameters = (): boolean => {
return isFullTextSearchEnabled() && (isServerlessAccount() || showCollectionThroughputInput());
};
const getAnalyticalStorageTtl = (): number => {
if (!isSynapseLinkEnabled()) {
return undefined;
@@ -226,7 +228,7 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
};
}
if (showFullTextSearch) {
if (showFullTextSearchParameters()) {
indexingPolicy.fullTextIndexes = fullTextIndexes;
}
@@ -385,7 +387,7 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
}}
/>
)}
{showFullTextSearch && (
{showFullTextSearchParameters() && (
<FullTextSearchComponent
{...{ fullTextPolicy, setFullTextPolicy, setFullTextIndexes, setFullTextPolicyValidated }}
/>

View File

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

View File

@@ -180,11 +180,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
? LocalStorageUtility.getEntryNumber(StorageKey.MaxWaitTimeInSeconds)
: Constants.Queries.DefaultMaxWaitTimeInSeconds,
);
const [queryControlEnabled, setQueryControlEnabled] = useState<boolean>(
LocalStorageUtility.hasItem(StorageKey.QueryControlEnabled)
? LocalStorageUtility.getEntryString(StorageKey.QueryControlEnabled) === "true"
: false,
);
const [maxDegreeOfParallelism, setMaxDegreeOfParallelism] = useState<number>(
LocalStorageUtility.hasItem(StorageKey.MaxDegreeOfParellism)
? LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism)
@@ -209,7 +204,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
!isEmulator;
const shouldShowGraphAutoVizOption = userContext.apiType === "Gremlin" && !isEmulator;
const shouldShowCrossPartitionOption = userContext.apiType !== "Gremlin" && !isEmulator;
const shouldShowEnhancedQueryControl = userContext.apiType === "SQL";
const shouldShowParallelismOption = userContext.apiType !== "Gremlin" && !isEmulator;
const showEnableEntraIdRbac =
isDataplaneRbacSupported(userContext.apiType) &&
@@ -387,7 +381,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
LocalStorageUtility.setEntryNumber(StorageKey.MaxWaitTimeInSeconds, MaxWaitTimeInSeconds);
LocalStorageUtility.setEntryString(StorageKey.ContainerPaginationEnabled, containerPaginationEnabled.toString());
LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, crossPartitionQueryEnabled.toString());
LocalStorageUtility.setEntryString(StorageKey.QueryControlEnabled, queryControlEnabled.toString());
LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, maxDegreeOfParallelism);
LocalStorageUtility.setEntryString(StorageKey.PriorityLevel, priorityLevel.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)}`,
);
logConsoleInfo(`${crossPartitionQueryEnabled ? "Enabled" : "Disabled"} cross-partition query feed option`);
logConsoleInfo(`${queryControlEnabled ? "Enabled" : "Disabled"} query control option`);
logConsoleInfo(
`Updated the max degree of parallelism query feed option to ${LocalStorageUtility.getEntryNumber(
StorageKey.MaxDegreeOfParellism,
@@ -768,6 +760,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
)}
</AccordionPanel>
</AccordionItem>
<AccordionItem value="5">
<AccordionHeader>
<div className={styles.header}>RU Limit</div>
@@ -950,38 +943,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
</AccordionPanel>
</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 && (
<AccordionItem value="10">
<AccordionHeader>

View File

@@ -495,51 +495,6 @@ exports[`Settings Pane should render Default properly 1`] = `
</div>
</AccordionPanel>
</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
value="10"
>

View File

@@ -30,21 +30,6 @@
margin: 0px auto;
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 {
.flex-display();

View File

@@ -126,12 +126,8 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
useDatabases.getState().sampleDataResourceTokenCollection
) {
return (
<Stack
className="splashStackContainer"
style={{ width: "66%", cursor: "pointer", margin: "40px auto" }}
tokens={{ childrenGap: 16 }}
>
<Stack className="splashStackRow" horizontal>
<Stack style={{ width: "66%", cursor: "pointer", margin: "40px auto" }} tokens={{ childrenGap: 16 }}>
<Stack horizontal tokens={{ childrenGap: 16 }}>
<SplashScreenButton
imgSrc={QuickStartIcon}
title={"Launch quick start"}
@@ -151,7 +147,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
}}
/>
</Stack>
<Stack className="splashStackRow" horizontal>
<Stack horizontal tokens={{ childrenGap: 16 }}>
{useQueryCopilot.getState().copilotEnabled && (
<SplashScreenButton
imgSrc={CopilotIcon}

View File

@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable no-console */
import { FeedOptions } from "@azure/cosmos";
import { FeedOptions, QueryOperationOptions } from "@azure/cosmos";
import { AuthType } from "AuthType";
import QueryError, { createMonacoErrorLocationResolver, createMonacoMarkersForQueryErrors } from "Common/QueryError";
import { SplitterDirection } from "Common/Splitter";
@@ -19,7 +19,7 @@ import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil";
import { useSelectedNode } from "Explorer/useSelectedNode";
import { KeyboardAction } from "KeyboardShortcuts";
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 { Allotment } from "allotment";
import { useClientWriteEnabled } from "hooks/useClientWriteEnabled";
@@ -55,8 +55,6 @@ import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPa
import { SaveQueryPane } from "../../Panes/SaveQueryPane/SaveQueryPane";
import TabsBase from "../TabsBase";
import "./QueryTabComponent.less";
import { useQueryMetadataStore } from "./useQueryMetadataStore"; // adjust path if needed
enum ToggleState {
Result,
@@ -198,10 +196,7 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
enabled: !!this.state.sqlQueryEditorContent && this.state.sqlQueryEditorContent.length > 0,
visible: true,
};
// const query=this.state.sqlQueryEditorContent;
// const db = this.props.collection.databaseId;
// const container = this.props.collection.id();
const isSaveQueryBtnEnabled = userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
this.saveQueryButton = {
enabled: isSaveQueryBtnEnabled,
@@ -265,10 +260,6 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
}
public onExecuteQueryClick = async (): Promise<void> => {
const query1=this.state.sqlQueryEditorContent;
const db = this.props.collection.databaseId;
const container = this.props.collection.id();
useQueryMetadataStore.getState().setMetadata(query1, db, container);
this._iterator = undefined;
setTimeout(async () => {
@@ -378,9 +369,22 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
this.setState({
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) =>
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.setState({
isExecuting: true,
@@ -420,9 +424,6 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
firstItemIndex,
queryDocuments,
);
if (queryResults.ruThresholdExceeded) {
this.props.tabsBaseInstance.isExecutionWarning(true);
}
this.setState({ queryResults, errors: [] });
} catch (error) {
this.props.tabsBaseInstance.isExecutionError(true);

View File

@@ -1,7 +1,5 @@
import { FontIcon } from "@fluentui/react";
import {
Button,
Checkbox,
DataGrid,
DataGridBody,
DataGridCell,
@@ -10,41 +8,30 @@ import {
DataGridRow,
SelectTabData,
SelectTabEvent,
Spinner,
Tab,
TabList,
Table,
TableBody,
TableCell,
TableColumnDefinition,
TableHeader,
TableRow,
createTableColumn
createTableColumn,
} from "@fluentui/react-components";
import { ArrowDownloadRegular, ChevronDown20Regular, ChevronRight20Regular, CircleFilled, CopyRegular } from "@fluentui/react-icons";
import copy from "clipboard-copy";
import { ArrowDownloadRegular, CopyRegular } from "@fluentui/react-icons";
import { HttpHeaders } from "Common/Constants";
import MongoUtility from "Common/MongoUtility";
import { QueryMetrics } from "Contracts/DataModels";
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
import { IDocument } from "Explorer/Tabs/QueryTab/QueryTabComponent";
import { useQueryTabStyles } from "Explorer/Tabs/QueryTab/Styles";
import { useQueryMetadataStore } from "Explorer/Tabs/QueryTab/useQueryMetadataStore";
import React, { useCallback, useEffect, useState } from "react";
import { userContext } from "UserContext";
import { logConsoleProgress } from "Utils/NotificationConsoleUtils";
import { client } from "../../../Common/CosmosClient";
import { handleError } from "../../../Common/ErrorHandlingUtils";
import copy from "clipboard-copy";
import React, { useCallback, useState } from "react";
import { ResultsViewProps } from "./QueryResultSection";
enum ResultsTabs {
Results = "results",
QueryStats = "queryStats",
IndexAdvisor = "indexadv",
}
const ResultsTab: React.FC<ResultsViewProps> = ({ queryResults, isMongoDB, executeQueryDocumentsPage }) => {
const styles = useQueryTabStyles();
/* eslint-disable react/prop-types */
const queryResultsString = queryResults
? isMongoDB
? MongoUtility.tojson(queryResults.documents, undefined, false)
@@ -60,172 +47,6 @@ const ResultsTab: React.FC<ResultsViewProps> = ({ queryResults, isMongoDB, execu
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 (
<>
<div className={styles.queryResultsBar}>
@@ -246,7 +67,6 @@ const ResultsTab: React.FC<ResultsViewProps> = ({ queryResults, isMongoDB, execu
aria-label="Copy"
onClick={onClickCopyResults}
/>
<ExportResults />
</div>
<div className={styles.queryResultsViewer}>
<EditorReact language={"json"} content={queryResultsString} isReadOnly={true} ariaLabel={"Query results"} />
@@ -392,8 +212,9 @@ const QueryStatsTab: React.FC<Pick<ResultsViewProps, "queryResults">> = ({ query
},
{
metric: "User defined function execution time",
value: `${aggregatedQueryMetrics.runtimeExecutionTimes?.userDefinedFunctionExecutionTime?.toString() || 0
} ms`,
value: `${
aggregatedQueryMetrics.runtimeExecutionTimes?.userDefinedFunctionExecutionTime?.toString() || 0
} ms`,
toolTip: "Total time spent executing user-defined functions",
},
{
@@ -534,394 +355,6 @@ const QueryStatsTab: React.FC<Pick<ResultsViewProps, "queryResults">> = ({ query
);
};
interface IIndexMetric {
index: string;
impact: string;
section: "Included" | "Not Included" | "Header";
}
const IndexAdvisorTab: React.FC = () => {
const { userQuery, databaseId, containerId } = useQueryMetadataStore();
const [loading, setLoading] = useState(true);
const [indexMetrics, setIndexMetrics] = useState<any>(null);
const [showIncluded, setShowIncluded] = useState(true);
const [showNotIncluded, setShowNotIncluded] = useState(true);
const [selectedIndexes, setSelectedIndexes] = useState<any[]>([]);
const [selectAll, setSelectAll] = useState(false);
const [updateMessageShown, setUpdateMessageShown] = useState(false);
const [included, setIncludedIndexes] = useState<IIndexMetric[]>([]);
const [notIncluded, setNotIncludedIndexes] = useState<IIndexMetric[]>([]);
useEffect(() => {
async function fetchIndexMetrics() {
const clearMessage = logConsoleProgress(`Querying items with IndexMetrics in container ${containerId}`);
try {
const querySpec = {
query: userQuery || "SELECT TOP 10 c.id FROM c WHERE c.Item = 'value1234' ",
};
const sdkResponse = await client()
.database(databaseId)
.container(containerId)
.items.query(querySpec, {
populateIndexMetrics: true,
})
.fetchAll();
setIndexMetrics(sdkResponse.indexMetrics);
} catch (error) {
handleError(error, "queryItemsWithIndexMetrics", `Error querying items from ${containerId}`);
} finally {
clearMessage();
setLoading(false);
}
}
if (userQuery && databaseId && containerId) {
fetchIndexMetrics();
}
}, [userQuery, databaseId, containerId]);
useEffect(() => {
if (!indexMetrics) return;
const included: any[] = [];
const notIncluded: any[] = [];
const lines = indexMetrics.split("\n").map((line: string) => line.trim()).filter(Boolean);
let currentSection = "";
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.startsWith("Utilized Single Indexes") || line.startsWith("Utilized Composite Indexes")) {
currentSection = "included";
} else if (line.startsWith("Potential Single Indexes") || line.startsWith("Potential Composite Indexes")) {
currentSection = "notIncluded";
} else if (line.startsWith("Index Spec:")) {
const index = line.replace("Index Spec:", "").trim();
const impactLine = lines[i + 1];
const impact = impactLine?.includes("Index Impact Score:") ? impactLine.split(":")[1].trim() : "Unknown";
const isComposite = index.includes(",");
const indexObj: any = { index, impact };
if (isComposite) {
indexObj.composite = index.split(",").map((part: string) => {
const [path, order] = part.trim().split(/\s+/);
return {
path: path.trim(),
order: order?.toLowerCase() === "desc" ? "descending" : "ascending",
};
});
} else {
let path = "/unknown/*";
const pathRegex = /\/[^\/\s*?]+(?:\/[^\/\s*?]+)*(\/\*|\?)/;
const match = index.match(pathRegex);
if (match) {
path = match[0];
} else {
const simplePathRegex = /\/[^\/\s]+/;
const simpleMatch = index.match(simplePathRegex);
if (simpleMatch) {
path = simpleMatch[0] + "/*";
}
}
indexObj.path = path;
}
if (currentSection === "included") {
included.push(indexObj);
} else if (currentSection === "notIncluded") {
notIncluded.push(indexObj);
}
}
}
setIncludedIndexes(included);
setNotIncludedIndexes(notIncluded);
}, [indexMetrics]);
useEffect(() => {
const allSelected = notIncluded.length > 0 && notIncluded.every((item) => selectedIndexes.some((s) => s.index === item.index));
setSelectAll(allSelected);
}, [selectedIndexes, notIncluded]);
const handleCheckboxChange = (indexObj: any, checked: boolean) => {
if (checked) {
setSelectedIndexes((prev) => [...prev, indexObj]);
} else {
setSelectedIndexes((prev) =>
prev.filter((item) => item.index !== indexObj.index)
);
}
};
const handleSelectAll = (checked: boolean) => {
setSelectAll(checked);
setSelectedIndexes(checked ? notIncluded : []);
};
const handleUpdatePolicy = async () => {
if (selectedIndexes.length === 0) {
console.log("No indexes selected for update");
return;
}
try {
const { resource: containerDef } = await client()
.database(databaseId)
.container(containerId)
.read();
const newIncludedPaths = selectedIndexes
.filter(index => !index.composite)
.map(index => {
return {
path: index.path,
};
});
const newCompositeIndexes = selectedIndexes
.filter(index => index.composite)
.map(index => index.composite);
const updatedPolicy = {
...containerDef.indexingPolicy,
includedPaths: [
...(containerDef.indexingPolicy?.includedPaths || []),
...newIncludedPaths,
],
compositeIndexes: [
...(containerDef.indexingPolicy?.compositeIndexes || []),
...newCompositeIndexes,
],
};
await client()
.database(databaseId)
.container(containerId)
.replace({
id: containerId,
partitionKey: containerDef.partitionKey,
indexingPolicy: updatedPolicy,
});
const newIncluded = [...included, ...notIncluded.filter(item =>
selectedIndexes.find(s => s.index === item.index)
)];
const newNotIncluded = notIncluded.filter(item =>
!selectedIndexes.find(s => s.index === item.index)
);
setSelectedIndexes([]);
setSelectAll(false);
setIndexMetricsFromParsed(newIncluded, newNotIncluded);
setUpdateMessageShown(true);
} catch (err) {
console.error("Failed to update indexing policy:", err);
}
};
const setIndexMetricsFromParsed = (included: { index: string; impact: string }[], notIncluded: { index: string; impact: string }[]) => {
const serialize = (sectionTitle: string, items: { index: string; impact: string }[], isUtilized: boolean) =>
items.length
? `${sectionTitle}\n` +
items
.map((item) => `Index Spec: ${item.index}\nIndex Impact Score: ${item.impact}`)
.join("\n") + "\n"
: "";
const composedMetrics =
serialize("Utilized Single Indexes", included, true) +
serialize("Potential Single Indexes", notIncluded, false);
setIndexMetrics(composedMetrics.trim());
};
const renderImpactDots = (impact: string) => {
let count = 0;
if (impact === "High") count = 3;
else if (impact === "Medium") count = 2;
else if (impact === "Low") count = 1;
return (
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
{Array.from({ length: count }).map((_, i) => (
<CircleFilled
key={i}
style={{
color: "#0078D4",
fontSize: "12px",
display: "inline-flex",
}}
/>
))}
</div>
);
};
const renderRow = (item: IIndexMetric, index: number) => {
const isHeader = item.section === "Header";
const isNotIncluded = item.section === "Not Included";
const isIncluded = item.section === "Included";
return (
<TableRow key={index}>
<TableCell colSpan={2}>
<div
style={{
display: "grid",
gridTemplateColumns: "30px 30px 1fr 50px 120px",
alignItems: "center",
gap: "8px",
}}>
{isNotIncluded ? (
<Checkbox
checked={selectedIndexes.some((selected) => selected.index === item.index)}
onChange={(_, data) => handleCheckboxChange(item, data.checked === true)}
/>
) : isHeader && item.index === "Not Included in Current Policy" && notIncluded.length > 0 ? (
<Checkbox
checked={selectAll}
onChange={(_, data) => handleSelectAll(data.checked === true)}
/>
) : (
<div style={{ width: "18px", height: "18px" }}></div>
)}
{isHeader ? (
<span
style={{ cursor: "pointer" }}
onClick={() => {
if (item.index === "Included in Current Policy") {
setShowIncluded(!showIncluded);
} else if (item.index === "Not Included in Current Policy") {
setShowNotIncluded(!showNotIncluded);
}
}}
>
{item.index === "Included in Current Policy"
? showIncluded
? <ChevronDown20Regular />
: <ChevronRight20Regular />
: showNotIncluded
? <ChevronDown20Regular />
: <ChevronRight20Regular />
}
</span>
) : (
<div style={{ width: "24px" }}></div>
)}
<div style={{ fontWeight: isHeader ? "bold" : "normal" }}>
{item.index}
</div>
<div style={{ fontSize: isHeader ? 0 : undefined }}>
{isHeader ? null : item.impact}
</div>
<div>
{isHeader ? null : renderImpactDots(item.impact)}
</div>
</div>
</TableCell>
</TableRow>
);
};
const generateIndexMetricItems = (
included: { index: string; impact: string }[],
notIncluded: { index: string; impact: string }[]
): IIndexMetric[] => {
const items: IIndexMetric[] = [];
items.push({ index: "Not Included in Current Policy", impact: "", section: "Header" });
if (showNotIncluded) {
notIncluded.forEach((item) =>
items.push({ ...item, section: "Not Included" })
);
}
items.push({ index: "Included in Current Policy", impact: "", section: "Header" });
if (showIncluded) {
included.forEach((item) =>
items.push({ ...item, section: "Included" })
);
}
return items;
};
if (loading) {
return <div>
<Spinner
size="small"
style={{
'--spinner-size': '16px',
'--spinner-thickness': '2px',
'--spinner-color': '#0078D4',
} as React.CSSProperties} />
</div>;
}
return (
<div>
<div style={{ padding: "1rem", fontSize: "1.2rem", display: "flex", alignItems: "center", gap: "0.5rem" }}>
{updateMessageShown ? (
<>
<span
style={{
width: 18,
height: 18,
borderRadius: "50%",
backgroundColor: "#107C10",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}>
<FontIcon iconName="CheckMark" style={{ color: "white", fontSize: 12 }} />
</span>
<span>
Your indexing policy has been updated with the new included paths. You may review the changes in Scale & Settings.
</span>
</>
) : (
"Here is an analysis on the indexes utilized for executing the query. Based on the analysis, Cosmos DB recommends adding the selected indexes to your indexing policy to optimize the performance of this particular query."
)}
</div>
<div style={{ padding: "1rem", fontSize: "1.3rem", fontWeight: "bold" }}>Indexes analysis</div>
<Table style={{ display: "block", alignItems: "center", marginBottom: "7rem" }}>
<TableHeader>
<TableRow >
<TableCell colSpan={2}>
<div
style={{
display: "grid",
gridTemplateColumns: "30px 30px 1fr 50px 120px",
alignItems: "center",
gap: "8px",
fontWeight: "bold",
}}
>
<div style={{ width: "18px", height: "18px" }}></div>
<div style={{ width: "24px" }}></div>
<div>Index</div>
<div><span style={{ whiteSpace: "nowrap" }}>Estimated Impact</span></div>
</div>
</TableCell>
</TableRow>
</TableHeader>
<TableBody>
{generateIndexMetricItems(included, notIncluded).map(renderRow)}
</TableBody>
</Table>
{selectedIndexes.length > 0 && (
<div style={{ padding: "1rem", marginTop: "-7rem", flexWrap: "wrap" }}>
<button
onClick={handleUpdatePolicy}
style={{
backgroundColor: "#0078D4",
color: "white",
padding: "8px 16px",
border: "none",
borderRadius: "4px",
cursor: "pointer",
marginTop: "1rem",
}}
>
Update Indexing Policy with selected index(es)
</button>
</div>
)}
</div>
);
};
export const ResultsView: React.FC<ResultsViewProps> = ({ isMongoDB, queryResults, executeQueryDocumentsPage }) => {
const styles = useQueryTabStyles();
const [activeTab, setActiveTab] = useState<ResultsTabs>(ResultsTabs.Results);
@@ -947,13 +380,6 @@ export const ResultsView: React.FC<ResultsViewProps> = ({ isMongoDB, queryResult
>
Query Stats
</Tab>
<Tab
data-test="QueryTab/ResultsPane/ResultsView/IndexAdvisorTab"
id={ResultsTabs.IndexAdvisor}
value={ResultsTabs.IndexAdvisor}
>
Index Advisor
</Tab>
</TabList>
<div className={styles.queryResultsTabContentContainer}>
{activeTab === ResultsTabs.Results && (
@@ -964,7 +390,6 @@ export const ResultsView: React.FC<ResultsViewProps> = ({ isMongoDB, queryResult
/>
)}
{activeTab === ResultsTabs.QueryStats && <QueryStatsTab queryResults={queryResults} />}
{activeTab === ResultsTabs.IndexAdvisor && <IndexAdvisorTab />}
</div>
</div>
);

View File

@@ -1,16 +0,0 @@
import create from "zustand";
interface QueryMetadataStore {
userQuery: string;
databaseId: string;
containerId: string;
setMetadata: (query1: string, db: string, container: string) => void;
}
export const useQueryMetadataStore = create<QueryMetadataStore>((set) => ({
userQuery: "",
databaseId: "",
containerId: "",
setMetadata: (query1, db, container) =>
set({ userQuery: query1, databaseId: db, containerId: container }),
}));

View File

@@ -18,7 +18,6 @@ import React, { MutableRefObject, useEffect, useRef, useState } from "react";
import loadingIcon from "../../../images/circular_loader_black_16x16.gif";
import errorIcon from "../../../images/close-black.svg";
import errorQuery from "../../../images/error_no_outline.svg";
import warningIconSvg from "../../../images/warning.svg";
import { useObservable } from "../../hooks/useObservable";
import { ReactTabKind, useTabs } from "../../hooks/useTabs";
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 }}>
{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) && (
<img className="loadingIcon" title="Loading" src={loadingIcon} alt="Loading" />
)}
@@ -198,20 +194,6 @@ const ErrorIcon = ({ tab, active }: { tab: Tab; active: boolean }) => (
</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 }) {
const ref = useRef<HTMLDivElement>();
const attrs = {

View File

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

View File

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

View File

@@ -20,3 +20,7 @@ export const isServerlessAccount = (): boolean => {
export const isVectorSearchEnabled = (): boolean => {
return userContext.apiType === "SQL" && isCapabilityEnabled(Constants.CapabilityNames.EnableNoSQLVectorSearch);
};
export const isFullTextSearchEnabled = (): boolean => {
return userContext.apiType === "SQL" && isCapabilityEnabled(Constants.CapabilityNames.EnableNoSQLFullTextSearch);
};

View File

@@ -23,7 +23,3 @@ export const logConsoleError = (msg: string): void => {
export const logConsoleInfo = (msg: string): void => {
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 { getRUThreshold, ruThresholdEnabled } from "Shared/StorageUtility";
import { userContext } from "UserContext";
import { logConsoleWarning } from "Utils/NotificationConsoleUtils";
import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels";
@@ -89,18 +86,6 @@ export const queryPagesUntilContentPresent = async (
results.roundTrips = roundTrips;
results.requestCharge = Number(results.requestCharge) + netRequestCharge;
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 = {
hasMoreResults: results.hasMoreResults,
itemCount: results.itemCount,

View File

@@ -103,7 +103,7 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
.forEach((tab) => tab.onCloseTabButtonClick()),
closeTab: (tab: TabsBase): void => {
let tabIndex: number;
const { activeTab, openedTabs, openedReactTabs } = get();
const { activeTab, openedTabs } = get();
const updatedTabs = openedTabs.filter((openedTab, index) => {
if (tab.tabId === openedTab.tabId) {
tabIndex = index;
@@ -127,10 +127,6 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
set({ openedTabs: updatedTabs });
if (updatedTabs.length === 0 && openedReactTabs.length > 0) {
set({ activeTab: undefined, activeReactTab: openedReactTabs[openedReactTabs.length - 1] });
}
get().persistTabsState();
},
closeAllNotebookTabs: (hardClose): void => {

View File

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

View File

@@ -78,18 +78,11 @@ const typescriptRule = {
exclude: /node_modules/,
};
const javascriptRule = {
test: /\.m?js$/,
resolve: {
fullySpecified: false,
},
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
/** @type {(_env: Record<string, string>, argv: Record<string, unknown>) => import("webpack").Configuration} */
module.exports = function (_env = {}, argv = {}) {
const mode = argv.mode || "development";
const rules = [fontRule, lessRule, imagesRule, cssRule, htmlRule, typescriptRule, javascriptRule];
const rules = [fontRule, lessRule, imagesRule, cssRule, htmlRule, typescriptRule];
const envVars = {
GIT_SHA: gitSha,
PORT: process.env.PORT || "1234",