diff --git a/src/Common/CosmosClient.ts b/src/Common/CosmosClient.ts index 2779e1ba1..79ed76434 100644 --- a/src/Common/CosmosClient.ts +++ b/src/Common/CosmosClient.ts @@ -3,9 +3,8 @@ import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizatio import { AuthorizationToken } from "Contracts/FabricMessageTypes"; import { checkDatabaseResourceTokensValidity } from "Platform/Fabric/FabricUtil"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; -import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils"; import { AuthType } from "../AuthType"; -import { BackendApi, PriorityLevel } from "../Common/Constants"; +import { PriorityLevel } from "../Common/Constants"; import * as Logger from "../Common/Logger"; import { Platform, configContext } from "../ConfigContext"; import { updateUserContext, userContext } from "../UserContext"; @@ -125,10 +124,6 @@ export async function getTokenFromAuthService( resourceType: string, resourceId?: string, ): Promise { - if (!useNewPortalBackendEndpoint(BackendApi.RuntimeProxy)) { - return getTokenFromAuthService_ToBeDeprecated(verb, resourceType, resourceId); - } - try { const host: string = configContext.PORTAL_BACKEND_ENDPOINT; const response: Response = await _global.fetch(host + "/api/connectionstring/runtimeproxy/authorizationtokens", { @@ -151,34 +146,6 @@ export async function getTokenFromAuthService( } } -export async function getTokenFromAuthService_ToBeDeprecated( - verb: string, - resourceType: string, - resourceId?: string, -): Promise { - try { - const host = configContext.BACKEND_ENDPOINT; - const response = await _global.fetch(host + "/api/guest/runtimeproxy/authorizationTokens", { - method: "POST", - headers: { - "content-type": "application/json", - "x-ms-encrypted-auth-token": userContext.accessToken, - }, - body: JSON.stringify({ - verb, - resourceType, - resourceId, - }), - }); - //TODO I am not sure why we have to parse the JSON again here. fetch should do it for us when we call .json() - const result = JSON.parse(await response.json()); - return result; - } catch (error) { - logConsoleError(`Failed to get authorization headers for ${resourceType}: ${getErrorMessage(error)}`); - return Promise.reject(error); - } -} - // The Capability is a bitmap, which cosmosdb backend decodes as per the below enum enum SDKSupportedCapabilities { None = 0, @@ -203,8 +170,10 @@ export function client(): Cosmos.CosmosClient { } let _defaultHeaders: Cosmos.CosmosHeaders = {}; + _defaultHeaders["x-ms-cosmos-sdk-supportedcapabilities"] = SDKSupportedCapabilities.None | SDKSupportedCapabilities.PartitionMerge; + _defaultHeaders["x-ms-cosmos-throughput-bucket"] = 1; if ( userContext.authType === AuthType.ConnectionString || diff --git a/src/Common/MongoProxyClient.test.ts b/src/Common/MongoProxyClient.test.ts index a63190651..0e41a6d35 100644 --- a/src/Common/MongoProxyClient.test.ts +++ b/src/Common/MongoProxyClient.test.ts @@ -4,16 +4,8 @@ import { configContext, resetConfigContext, updateConfigContext } from "../Confi import { DatabaseAccount } from "../Contracts/DataModels"; import { Collection } from "../Contracts/ViewModels"; import DocumentId from "../Explorer/Tree/DocumentId"; -import { extractFeatures } from "../Platform/Hosted/extractFeatures"; import { updateUserContext } from "../UserContext"; -import { - deleteDocument, - getEndpoint, - getFeatureEndpointOrDefault, - queryDocuments, - readDocument, - updateDocument, -} from "./MongoProxyClient"; +import { deleteDocuments, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient"; const databaseId = "testDB"; @@ -196,20 +188,8 @@ describe("MongoProxyClient", () => { expect.any(Object), ); }); - - it("builds the correct proxy URL in development", () => { - updateConfigContext({ - MONGO_BACKEND_ENDPOINT: "https://localhost:1234", - globallyEnabledMongoAPIs: [], - }); - updateDocument(databaseId, collection, documentId, "{}"); - expect(window.fetch).toHaveBeenCalledWith( - `${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`, - expect.any(Object), - ); - }); }); - describe("deleteDocument", () => { + describe("deleteDocuments", () => { beforeEach(() => { resetConfigContext(); updateUserContext({ @@ -226,9 +206,9 @@ describe("MongoProxyClient", () => { }); it("builds the correct URL", () => { - deleteDocument(databaseId, collection, documentId); + deleteDocuments(databaseId, collection, [documentId]); expect(window.fetch).toHaveBeenCalledWith( - `${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`, + `${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer/bulkdelete`, expect.any(Object), ); }); @@ -238,9 +218,9 @@ describe("MongoProxyClient", () => { MONGO_PROXY_ENDPOINT: "https://localhost:1234", globallyEnabledMongoAPIs: [], }); - deleteDocument(databaseId, collection, documentId); + deleteDocuments(databaseId, collection, [documentId]); expect(window.fetch).toHaveBeenCalledWith( - `${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`, + `${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer/bulkdelete`, expect.any(Object), ); }); @@ -275,33 +255,4 @@ describe("MongoProxyClient", () => { expect(endpoint).toEqual(`${configContext.MONGO_PROXY_ENDPOINT}/api/connectionstring/mongo/explorer`); }); }); - - describe("getFeatureEndpointOrDefault", () => { - beforeEach(() => { - resetConfigContext(); - updateConfigContext({ - MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod, - globallyEnabledMongoAPIs: [], - }); - const params = new URLSearchParams({ - "feature.mongoProxyEndpoint": MongoProxyEndpoints.Prod, - "feature.mongoProxyAPIs": "readDocument|createDocument", - }); - const features = extractFeatures(params); - updateUserContext({ - authType: AuthType.AAD, - features: features, - }); - }); - - it("returns a local endpoint", () => { - const endpoint = getFeatureEndpointOrDefault("readDocument"); - expect(endpoint).toEqual(`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`); - }); - - it("returns a production endpoint", () => { - const endpoint = getFeatureEndpointOrDefault("DeleteDocument"); - expect(endpoint).toEqual(`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`); - }); - }); }); diff --git a/src/Common/MongoProxyClient.ts b/src/Common/MongoProxyClient.ts index c509b7fc8..89156e50c 100644 --- a/src/Common/MongoProxyClient.ts +++ b/src/Common/MongoProxyClient.ts @@ -1,20 +1,13 @@ import { Constants as CosmosSDKConstants } from "@azure/cosmos"; -import { - allowedMongoProxyEndpoints_ToBeDeprecated, - defaultAllowedMongoProxyEndpoints, - validateEndpoint, -} from "Utils/EndpointUtils"; -import queryString from "querystring"; import { AuthType } from "../AuthType"; import { configContext } from "../ConfigContext"; import * as DataModels from "../Contracts/DataModels"; import { MessageTypes } from "../Contracts/ExplorerContracts"; import { Collection } from "../Contracts/ViewModels"; import DocumentId from "../Explorer/Tree/DocumentId"; -import { hasFlag } from "../Platform/Hosted/extractFeatures"; import { userContext } from "../UserContext"; import { logConsoleError } from "../Utils/NotificationConsoleUtils"; -import { ApiType, ContentType, HttpHeaders, HttpStatusCodes, MongoProxyApi, MongoProxyEndpoints } from "./Constants"; +import { ApiType, ContentType, HttpHeaders, HttpStatusCodes } from "./Constants"; import { MinimalQueryIterator } from "./IteratorUtilities"; import { sendMessage } from "./MessageHandler"; @@ -67,10 +60,6 @@ export function queryDocuments( query: string, continuationToken?: string, ): Promise { - if (!useMongoProxyEndpoint(MongoProxyApi.ResourceList) || !useMongoProxyEndpoint(MongoProxyApi.QueryDocuments)) { - return queryDocuments_ToBeDeprecated(databaseId, collection, isResourceList, query, continuationToken); - } - const { databaseAccount } = userContext; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; const params = { @@ -89,7 +78,7 @@ export function queryDocuments( query, }; - const endpoint = getFeatureEndpointOrDefault(MongoProxyApi.ResourceList) || ""; + const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT) || ""; const headers = { ...defaultHeaders, @@ -127,76 +116,11 @@ export function queryDocuments( }); } -function queryDocuments_ToBeDeprecated( - databaseId: string, - collection: Collection, - isResourceList: boolean, - query: string, - continuationToken?: string, -): Promise { - const { databaseAccount } = userContext; - const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; - const params = { - db: databaseId, - coll: collection.id(), - resourceUrl: `${resourceEndpoint}dbs/${databaseId}/colls/${collection.id()}/docs/`, - rid: collection.rid, - rtype: "docs", - sid: userContext.subscriptionId, - rg: userContext.resourceGroup, - dba: databaseAccount.name, - pk: - collection && collection.partitionKey && !collection.partitionKey.systemKey - ? collection.partitionKeyProperties?.[0] - : "", - }; - - const endpoint = getFeatureEndpointOrDefault("resourcelist") || ""; - - const headers = { - ...defaultHeaders, - ...authHeaders(), - [CosmosSDKConstants.HttpHeaders.IsQuery]: "true", - [CosmosSDKConstants.HttpHeaders.PopulateQueryMetrics]: "true", - [CosmosSDKConstants.HttpHeaders.EnableScanInQuery]: "true", - [CosmosSDKConstants.HttpHeaders.EnableCrossPartitionQuery]: "true", - [CosmosSDKConstants.HttpHeaders.ParallelizeCrossPartitionQuery]: "true", - [HttpHeaders.contentType]: "application/query+json", - }; - - if (continuationToken) { - headers[CosmosSDKConstants.HttpHeaders.Continuation] = continuationToken; - } - - const path = isResourceList ? "/resourcelist" : ""; - - return window - .fetch(`${endpoint}${path}?${queryString.stringify(params)}`, { - method: "POST", - body: JSON.stringify({ query }), - headers, - }) - .then(async (response) => { - if (response.ok) { - return { - continuationToken: response.headers.get(CosmosSDKConstants.HttpHeaders.Continuation), - documents: (await response.json()).Documents as DataModels.DocumentId[], - headers: response.headers, - }; - } - await errorHandling(response, "querying documents", params); - return undefined; - }); -} - export function readDocument( databaseId: string, collection: Collection, documentId: DocumentId, ): Promise { - if (!useMongoProxyEndpoint(MongoProxyApi.ReadDocument)) { - return readDocument_ToBeDeprecated(databaseId, collection, documentId); - } const { databaseAccount } = userContext; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; const idComponents = documentId.self.split("/"); @@ -217,7 +141,7 @@ export function readDocument( : "", }; - const endpoint = getFeatureEndpointOrDefault(MongoProxyApi.ReadDocument); + const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT); return window .fetch(endpoint, { @@ -237,61 +161,12 @@ export function readDocument( }); } -export function readDocument_ToBeDeprecated( - databaseId: string, - collection: Collection, - documentId: DocumentId, -): Promise { - const { databaseAccount } = userContext; - const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; - const idComponents = documentId.self.split("/"); - const path = idComponents.slice(0, 4).join("/"); - const rid = encodeURIComponent(idComponents[5]); - const params = { - db: databaseId, - coll: collection.id(), - resourceUrl: `${resourceEndpoint}${path}/${rid}`, - rid, - rtype: "docs", - sid: userContext.subscriptionId, - rg: userContext.resourceGroup, - dba: databaseAccount.name, - pk: - documentId && documentId.partitionKey && !documentId.partitionKey.systemKey - ? documentId.partitionKeyProperties?.[0] - : "", - }; - - const endpoint = getFeatureEndpointOrDefault("readDocument"); - - return window - .fetch(`${endpoint}?${queryString.stringify(params)}`, { - method: "GET", - headers: { - ...defaultHeaders, - ...authHeaders(), - [CosmosSDKConstants.HttpHeaders.PartitionKey]: encodeURIComponent( - JSON.stringify(documentId.partitionKeyHeader()), - ), - }, - }) - .then(async (response) => { - if (response.ok) { - return response.json(); - } - return await errorHandling(response, "reading document", params); - }); -} - export function createDocument( databaseId: string, collection: Collection, partitionKeyProperty: string, documentContent: unknown, ): Promise { - if (!useMongoProxyEndpoint(MongoProxyApi.CreateDocument)) { - return createDocument_ToBeDeprecated(databaseId, collection, partitionKeyProperty, documentContent); - } const { databaseAccount } = userContext; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; const params = { @@ -308,7 +183,7 @@ export function createDocument( documentContent: JSON.stringify(documentContent), }; - const endpoint = getFeatureEndpointOrDefault(MongoProxyApi.CreateDocument); + const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT); return window .fetch(`${endpoint}/createDocument`, { @@ -328,54 +203,12 @@ export function createDocument( }); } -export function createDocument_ToBeDeprecated( - databaseId: string, - collection: Collection, - partitionKeyProperty: string, - documentContent: unknown, -): Promise { - const { databaseAccount } = userContext; - const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; - const params = { - db: databaseId, - coll: collection.id(), - resourceUrl: `${resourceEndpoint}dbs/${databaseId}/colls/${collection.id()}/docs/`, - rid: collection.rid, - rtype: "docs", - sid: userContext.subscriptionId, - rg: userContext.resourceGroup, - dba: databaseAccount.name, - pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "", - }; - - const endpoint = getFeatureEndpointOrDefault("createDocument"); - - return window - .fetch(`${endpoint}/resourcelist?${queryString.stringify(params)}`, { - method: "POST", - body: JSON.stringify(documentContent), - headers: { - ...defaultHeaders, - ...authHeaders(), - }, - }) - .then(async (response) => { - if (response.ok) { - return response.json(); - } - return await errorHandling(response, "creating document", params); - }); -} - export function updateDocument( databaseId: string, collection: Collection, documentId: DocumentId, documentContent: string, ): Promise { - if (!useMongoProxyEndpoint(MongoProxyApi.UpdateDocument)) { - return updateDocument_ToBeDeprecated(databaseId, collection, documentId, documentContent); - } const { databaseAccount } = userContext; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; const idComponents = documentId.self.split("/"); @@ -396,7 +229,7 @@ export function updateDocument( : "", documentContent, }; - const endpoint = getFeatureEndpointOrDefault(MongoProxyApi.UpdateDocument); + const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT); return window .fetch(endpoint, { @@ -417,139 +250,6 @@ export function updateDocument( }); } -export function updateDocument_ToBeDeprecated( - databaseId: string, - collection: Collection, - documentId: DocumentId, - documentContent: string, -): Promise { - const { databaseAccount } = userContext; - const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; - const idComponents = documentId.self.split("/"); - const path = idComponents.slice(0, 5).join("/"); - const rid = encodeURIComponent(idComponents[5]); - const params = { - db: databaseId, - coll: collection.id(), - resourceUrl: `${resourceEndpoint}${path}/${rid}`, - rid, - rtype: "docs", - sid: userContext.subscriptionId, - rg: userContext.resourceGroup, - dba: databaseAccount.name, - pk: - documentId && documentId.partitionKey && !documentId.partitionKey.systemKey - ? documentId.partitionKeyProperties?.[0] - : "", - }; - const endpoint = getFeatureEndpointOrDefault("updateDocument"); - - return window - .fetch(`${endpoint}?${queryString.stringify(params)}`, { - method: "PUT", - body: documentContent, - headers: { - ...defaultHeaders, - ...authHeaders(), - [HttpHeaders.contentType]: ContentType.applicationJson, - [CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()), - }, - }) - .then(async (response) => { - if (response.ok) { - return response.json(); - } - return await errorHandling(response, "updating document", params); - }); -} - -export function deleteDocument(databaseId: string, collection: Collection, documentId: DocumentId): Promise { - if (!useMongoProxyEndpoint(MongoProxyApi.DeleteDocument)) { - return deleteDocument_ToBeDeprecated(databaseId, collection, documentId); - } - const { databaseAccount } = userContext; - const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; - const idComponents = documentId.self.split("/"); - const path = idComponents.slice(0, 5).join("/"); - const rid = encodeURIComponent(idComponents[5]); - const params = { - databaseID: databaseId, - collectionID: collection.id(), - resourceUrl: `${resourceEndpoint}${path}/${rid}`, - resourceID: rid, - resourceType: "docs", - subscriptionID: userContext.subscriptionId, - resourceGroup: userContext.resourceGroup, - databaseAccountName: databaseAccount.name, - partitionKey: - documentId && documentId.partitionKey && !documentId.partitionKey.systemKey - ? documentId.partitionKeyProperties?.[0] - : "", - }; - const endpoint = getFeatureEndpointOrDefault(MongoProxyApi.DeleteDocument); - - return window - .fetch(endpoint, { - method: "DELETE", - body: JSON.stringify(params), - headers: { - ...defaultHeaders, - ...authHeaders(), - [HttpHeaders.contentType]: ContentType.applicationJson, - }, - }) - .then(async (response) => { - if (response.ok) { - return undefined; - } - return await errorHandling(response, "deleting document", params); - }); -} - -export function deleteDocument_ToBeDeprecated( - databaseId: string, - collection: Collection, - documentId: DocumentId, -): Promise { - const { databaseAccount } = userContext; - const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; - const idComponents = documentId.self.split("/"); - const path = idComponents.slice(0, 5).join("/"); - const rid = encodeURIComponent(idComponents[5]); - const params = { - db: databaseId, - coll: collection.id(), - resourceUrl: `${resourceEndpoint}${path}/${rid}`, - rid, - rtype: "docs", - sid: userContext.subscriptionId, - rg: userContext.resourceGroup, - dba: databaseAccount.name, - pk: - documentId && documentId.partitionKey && !documentId.partitionKey.systemKey - ? documentId.partitionKeyProperties?.[0] - : "", - }; - const endpoint = getFeatureEndpointOrDefault("deleteDocument"); - - return window - .fetch(`${endpoint}?${queryString.stringify(params)}`, { - method: "DELETE", - headers: { - ...defaultHeaders, - ...authHeaders(), - [HttpHeaders.contentType]: ContentType.applicationJson, - [CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()), - }, - }) - .then(async (response) => { - if (response.ok) { - return undefined; - } - return await errorHandling(response, "deleting document", params); - }); -} - export function deleteDocuments( databaseId: string, collection: Collection, @@ -575,7 +275,7 @@ export function deleteDocuments( resourceGroup: userContext.resourceGroup, databaseAccountName: databaseAccount.name, }; - const endpoint = getFeatureEndpointOrDefault(MongoProxyApi.BulkDelete); + const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT); return window .fetch(`${endpoint}/bulkdelete`, { @@ -599,9 +299,6 @@ export function deleteDocuments( export function createMongoCollectionWithProxy( params: DataModels.CreateCollectionParams, ): Promise { - if (!useMongoProxyEndpoint(MongoProxyApi.CreateCollectionWithProxy)) { - return createMongoCollectionWithProxy_ToBeDeprecated(params); - } const { databaseAccount } = userContext; const shardKey: string = params.partitionKey?.paths[0]; @@ -622,7 +319,7 @@ export function createMongoCollectionWithProxy( isSharded: !!shardKey, }; - const endpoint = getFeatureEndpointOrDefault(MongoProxyApi.CreateCollectionWithProxy); + const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT); return window .fetch(`${endpoint}/createCollection`, { @@ -642,70 +339,6 @@ export function createMongoCollectionWithProxy( }); } -export function createMongoCollectionWithProxy_ToBeDeprecated( - params: DataModels.CreateCollectionParams, -): Promise { - const { databaseAccount } = userContext; - const shardKey: string = params.partitionKey?.paths[0]; - const mongoParams: DataModels.MongoParameters = { - resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint, - db: params.databaseId, - coll: params.collectionId, - pk: shardKey, - offerThroughput: params.autoPilotMaxThroughput || params.offerThroughput, - cd: params.createNewDatabase, - st: params.databaseLevelThroughput, - is: !!shardKey, - rid: "", - rtype: "colls", - sid: userContext.subscriptionId, - rg: userContext.resourceGroup, - dba: databaseAccount.name, - isAutoPilot: !!params.autoPilotMaxThroughput, - }; - - const endpoint = getFeatureEndpointOrDefault("createCollectionWithProxy"); - - return window - .fetch( - `${endpoint}/createCollection?${queryString.stringify( - mongoParams as unknown as queryString.ParsedUrlQueryInput, - )}`, - { - method: "POST", - headers: { - ...defaultHeaders, - ...authHeaders(), - [HttpHeaders.contentType]: "application/json", - }, - }, - ) - .then(async (response) => { - if (response.ok) { - return response.json(); - } - return await errorHandling(response, "creating collection", mongoParams); - }); -} -export function getFeatureEndpointOrDefault(feature: string): string { - let endpoint; - if (useMongoProxyEndpoint(feature)) { - endpoint = configContext.MONGO_PROXY_ENDPOINT; - } else { - const allowedMongoProxyEndpoints = configContext.allowedMongoProxyEndpoints || [ - ...defaultAllowedMongoProxyEndpoints, - ...allowedMongoProxyEndpoints_ToBeDeprecated, - ]; - endpoint = - hasFlag(userContext.features.mongoProxyAPIs, feature) && - validateEndpoint(userContext.features.mongoProxyEndpoint, allowedMongoProxyEndpoints) - ? userContext.features.mongoProxyEndpoint - : configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT; - } - - return getEndpoint(endpoint); -} - export function getEndpoint(endpoint: string): string { let url = endpoint + "/api/mongo/explorer"; @@ -719,84 +352,6 @@ export function getEndpoint(endpoint: string): string { return url; } -export function useMongoProxyEndpoint(mongoProxyApi: string): boolean { - const mongoProxyEnvironmentMap: { [key: string]: string[] } = { - [MongoProxyApi.ResourceList]: [ - MongoProxyEndpoints.Development, - MongoProxyEndpoints.Mpac, - MongoProxyEndpoints.Prod, - MongoProxyEndpoints.Fairfax, - MongoProxyEndpoints.Mooncake, - ], - [MongoProxyApi.QueryDocuments]: [ - MongoProxyEndpoints.Development, - MongoProxyEndpoints.Mpac, - MongoProxyEndpoints.Prod, - MongoProxyEndpoints.Fairfax, - MongoProxyEndpoints.Mooncake, - ], - [MongoProxyApi.CreateDocument]: [ - MongoProxyEndpoints.Development, - MongoProxyEndpoints.Mpac, - MongoProxyEndpoints.Prod, - MongoProxyEndpoints.Fairfax, - MongoProxyEndpoints.Mooncake, - ], - [MongoProxyApi.ReadDocument]: [ - MongoProxyEndpoints.Development, - MongoProxyEndpoints.Mpac, - MongoProxyEndpoints.Prod, - MongoProxyEndpoints.Fairfax, - MongoProxyEndpoints.Mooncake, - ], - [MongoProxyApi.UpdateDocument]: [ - MongoProxyEndpoints.Development, - MongoProxyEndpoints.Mpac, - MongoProxyEndpoints.Prod, - MongoProxyEndpoints.Fairfax, - MongoProxyEndpoints.Mooncake, - ], - [MongoProxyApi.DeleteDocument]: [ - MongoProxyEndpoints.Development, - MongoProxyEndpoints.Mpac, - MongoProxyEndpoints.Prod, - MongoProxyEndpoints.Fairfax, - MongoProxyEndpoints.Mooncake, - ], - [MongoProxyApi.CreateCollectionWithProxy]: [ - MongoProxyEndpoints.Development, - MongoProxyEndpoints.Mpac, - MongoProxyEndpoints.Prod, - MongoProxyEndpoints.Fairfax, - MongoProxyEndpoints.Mooncake, - ], - [MongoProxyApi.LegacyMongoShell]: [ - MongoProxyEndpoints.Development, - MongoProxyEndpoints.Mpac, - MongoProxyEndpoints.Prod, - MongoProxyEndpoints.Fairfax, - MongoProxyEndpoints.Mooncake, - ], - [MongoProxyApi.BulkDelete]: [ - MongoProxyEndpoints.Development, - MongoProxyEndpoints.Mpac, - MongoProxyEndpoints.Prod, - MongoProxyEndpoints.Fairfax, - MongoProxyEndpoints.Mooncake, - ], - }; - - if (!mongoProxyEnvironmentMap[mongoProxyApi] || !configContext.MONGO_PROXY_ENDPOINT) { - return false; - } - - if (configContext.globallyEnabledMongoAPIs.includes(mongoProxyApi)) { - return true; - } - - return mongoProxyEnvironmentMap[mongoProxyApi].includes(configContext.MONGO_PROXY_ENDPOINT); -} - export class ThrottlingError extends Error { constructor(message: string) { super(message); diff --git a/src/Common/dataAccess/readCollectionOffer.ts b/src/Common/dataAccess/readCollectionOffer.ts index e52df1f60..6fb6e9e4b 100644 --- a/src/Common/dataAccess/readCollectionOffer.ts +++ b/src/Common/dataAccess/readCollectionOffer.ts @@ -105,6 +105,8 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri ? parseInt(resource.softAllowedMaximumThroughput) : resource.softAllowedMaximumThroughput; + const throughputBuckets = resource?.throughputBuckets; + if (autoscaleSettings) { return { id: offerId, @@ -114,6 +116,7 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri offerReplacePending: resource.offerReplacePending === "true", instantMaximumThroughput, softAllowedMaximumThroughput, + throughputBuckets, }; } @@ -125,6 +128,7 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri offerReplacePending: resource.offerReplacePending === "true", instantMaximumThroughput, softAllowedMaximumThroughput, + throughputBuckets, }; } diff --git a/src/Common/dataAccess/updateOffer.ts b/src/Common/dataAccess/updateOffer.ts index 0e507ce37..4d26ca68d 100644 --- a/src/Common/dataAccess/updateOffer.ts +++ b/src/Common/dataAccess/updateOffer.ts @@ -1,6 +1,6 @@ import { OfferDefinition, RequestOptions } from "@azure/cosmos"; import { AuthType } from "../../AuthType"; -import { Offer, SDKOfferDefinition, UpdateOfferParams } from "../../Contracts/DataModels"; +import { Offer, SDKOfferDefinition, ThroughputBucket, UpdateOfferParams } from "../../Contracts/DataModels"; import { userContext } from "../../UserContext"; import { migrateCassandraKeyspaceToAutoscale, @@ -359,6 +359,13 @@ const createUpdateOfferBody = (params: UpdateOfferParams): ThroughputSettingsUpd body.properties.resource.throughput = params.manualThroughput; } + if (params.throughputBuckets) { + const throughputBuckets = params.throughputBuckets.filter( + (bucket: ThroughputBucket) => bucket.maxThroughputPercentage !== 100, + ); + body.properties.resource.throughputBuckets = throughputBuckets; + } + return body; }; diff --git a/src/ConfigContext.ts b/src/ConfigContext.ts index 2e4b4cda0..f09e5feb6 100644 --- a/src/ConfigContext.ts +++ b/src/ConfigContext.ts @@ -12,7 +12,6 @@ import { allowedGraphEndpoints, allowedHostedExplorerEndpoints, allowedJunoOrigins, - allowedMongoBackendEndpoints, allowedMsalRedirectEndpoints, defaultAllowedArmEndpoints, defaultAllowedBackendEndpoints, @@ -50,10 +49,8 @@ export interface ConfigContext { CATALOG_API_KEY: string; ARCADIA_ENDPOINT: string; ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string; - BACKEND_ENDPOINT?: string; PORTAL_BACKEND_ENDPOINT: string; NEW_BACKEND_APIS?: BackendApi[]; - MONGO_BACKEND_ENDPOINT?: string; MONGO_PROXY_ENDPOINT: string; CASSANDRA_PROXY_ENDPOINT: string; NEW_CASSANDRA_APIS?: string[]; @@ -109,7 +106,6 @@ let configContext: Readonly = { GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1189306 GITHUB_TEST_ENV_CLIENT_ID: "b63fc8cbf87fd3c6e2eb", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1777772 JUNO_ENDPOINT: JunoEndpoints.Prod, - BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com", PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod, MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod, CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod, @@ -152,15 +148,6 @@ export function updateConfigContext(newContext: Partial): void { delete newContext.ARCADIA_ENDPOINT; } - if ( - !validateEndpoint( - newContext.BACKEND_ENDPOINT, - configContext.allowedBackendEndpoints || defaultAllowedBackendEndpoints, - ) - ) { - delete newContext.BACKEND_ENDPOINT; - } - if ( !validateEndpoint( newContext.MONGO_PROXY_ENDPOINT, @@ -170,10 +157,6 @@ export function updateConfigContext(newContext: Partial): void { delete newContext.MONGO_PROXY_ENDPOINT; } - if (!validateEndpoint(newContext.MONGO_BACKEND_ENDPOINT, allowedMongoBackendEndpoints)) { - delete newContext.MONGO_BACKEND_ENDPOINT; - } - if ( !validateEndpoint( newContext.CASSANDRA_PROXY_ENDPOINT, diff --git a/src/Contracts/DataModels.ts b/src/Contracts/DataModels.ts index c0c581f28..d2bba68d8 100644 --- a/src/Contracts/DataModels.ts +++ b/src/Contracts/DataModels.ts @@ -276,6 +276,12 @@ export interface Offer { offerReplacePending: boolean; instantMaximumThroughput?: number; softAllowedMaximumThroughput?: number; + throughputBuckets?: ThroughputBucket[]; +} + +export interface ThroughputBucket { + id: number; + maxThroughputPercentage: number; } export interface SDKOfferDefinition extends Resource { @@ -398,6 +404,7 @@ export interface UpdateOfferParams { collectionId?: string; migrateToAutoPilot?: boolean; migrateToManual?: boolean; + throughputBuckets?: ThroughputBucket[]; } export interface Notification { diff --git a/src/Contracts/ViewModels.ts b/src/Contracts/ViewModels.ts index 0560c8931..83afa9ddb 100644 --- a/src/Contracts/ViewModels.ts +++ b/src/Contracts/ViewModels.ts @@ -406,7 +406,6 @@ export interface DataExplorerInputsFrame { csmEndpoint?: string; dnsSuffix?: string; serverId?: string; - extensionEndpoint?: string; portalBackendEndpoint?: string; mongoProxyEndpoint?: string; cassandraProxyEndpoint?: string; diff --git a/src/Explorer/Controls/Settings/SettingsComponent.test.tsx b/src/Explorer/Controls/Settings/SettingsComponent.test.tsx index a97ee8f45..5a7a47def 100644 --- a/src/Explorer/Controls/Settings/SettingsComponent.test.tsx +++ b/src/Explorer/Controls/Settings/SettingsComponent.test.tsx @@ -1,5 +1,7 @@ +import { AuthType } from "AuthType"; import { shallow } from "enzyme"; import ko from "knockout"; +import { Features } from "Platform/Hosted/extractFeatures"; import React from "react"; import { updateCollection } from "../../../Common/dataAccess/updateCollection"; import { updateOffer } from "../../../Common/dataAccess/updateOffer"; @@ -247,4 +249,42 @@ describe("SettingsComponent", () => { expect(conflictResolutionPolicy.mode).toEqual(DataModels.ConflictResolutionMode.Custom); expect(conflictResolutionPolicy.conflictResolutionProcedure).toEqual(expectSprocPath); }); + + it("should save throughput bucket changes when Save button is clicked", async () => { + updateUserContext({ + apiType: "SQL", + features: { enableThroughputBuckets: true } as Features, + authType: AuthType.AAD, + }); + + const wrapper = shallow(); + + const settingsComponentInstance = wrapper.instance() as SettingsComponent; + const isEnabled = settingsComponentInstance["throughputBucketsEnabled"]; + expect(isEnabled).toBe(true); + + wrapper.setState({ + isThroughputBucketsSaveable: true, + throughputBuckets: [ + { id: 1, maxThroughputPercentage: 70 }, + { id: 2, maxThroughputPercentage: 60 }, + ], + }); + + await settingsComponentInstance.onSaveClick(); + + expect(updateOffer).toHaveBeenCalledWith({ + databaseId: collection.databaseId, + collectionId: collection.id(), + currentOffer: expect.any(Object), + autopilotThroughput: collection.offer().autoscaleMaxThroughput, + manualThroughput: collection.offer().manualThroughput, + throughputBuckets: [ + { id: 1, maxThroughputPercentage: 70 }, + { id: 2, maxThroughputPercentage: 60 }, + ], + }); + + expect(wrapper.state("isThroughputBucketsSaveable")).toBe(false); + }); }); diff --git a/src/Explorer/Controls/Settings/SettingsComponent.tsx b/src/Explorer/Controls/Settings/SettingsComponent.tsx index 57bf5b13e..720bef874 100644 --- a/src/Explorer/Controls/Settings/SettingsComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsComponent.tsx @@ -7,6 +7,10 @@ import { ContainerPolicyComponent, ContainerPolicyComponentProps, } from "Explorer/Controls/Settings/SettingsSubComponents/ContainerPolicyComponent"; +import { + ThroughputBucketsComponent, + ThroughputBucketsComponentProps, +} from "Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent"; import { useDatabases } from "Explorer/useDatabases"; import { isFullTextSearchEnabled, isVectorSearchEnabled } from "Utils/CapabilityUtils"; import { isRunningOnPublicCloud } from "Utils/CloudUtils"; @@ -86,6 +90,8 @@ export interface SettingsComponentState { wasAutopilotOriginallySet: boolean; isScaleSaveable: boolean; isScaleDiscardable: boolean; + throughputBuckets: DataModels.ThroughputBucket[]; + throughputBucketsBaseline: DataModels.ThroughputBucket[]; throughputError: string; timeToLive: TtlType; @@ -104,6 +110,7 @@ export interface SettingsComponentState { changeFeedPolicyBaseline: ChangeFeedPolicyState; isSubSettingsSaveable: boolean; isSubSettingsDiscardable: boolean; + isThroughputBucketsSaveable: boolean; vectorEmbeddingPolicy: DataModels.VectorEmbeddingPolicy; vectorEmbeddingPolicyBaseline: DataModels.VectorEmbeddingPolicy; @@ -158,6 +165,7 @@ export class SettingsComponent extends React.Component this.setState({ indexingPolicyContent: newIndexingPolicy }); + private onThroughputBucketsSaveableChange = (isSaveable: boolean): void => { + this.setState({ isThroughputBucketsSaveable: isSaveable }); + }; + private resetShouldDiscardContainerPolicies = (): void => this.setState({ shouldDiscardContainerPolicies: false }); private resetShouldDiscardIndexingPolicy = (): void => this.setState({ shouldDiscardIndexingPolicy: false }); @@ -749,9 +773,13 @@ export class SettingsComponent extends React.Component { + this.setState({ throughputBuckets }); + }; + private onAutoPilotSelected = (isAutoPilotSelected: boolean): void => this.setState({ isAutoPilotSelected: isAutoPilotSelected }); @@ -1029,6 +1061,24 @@ export class SettingsComponent extends React.Component, + }); + } + const pivotProps: IPivotProps = { onLinkClick: this.onPivotChange, selectedKey: SettingsV2TabTypes[this.state.selectedTab], diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent.test.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent.test.tsx new file mode 100644 index 000000000..460dfa252 --- /dev/null +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent.test.tsx @@ -0,0 +1,177 @@ +import "@testing-library/jest-dom"; +import { fireEvent, render, screen } from "@testing-library/react"; +import React from "react"; +import { ThroughputBucketsComponent } from "./ThroughputBucketsComponent"; + +describe("ThroughputBucketsComponent", () => { + const mockOnBucketsChange = jest.fn(); + const mockOnSaveableChange = jest.fn(); + + const defaultProps = { + currentBuckets: [ + { id: 1, maxThroughputPercentage: 50 }, + { id: 2, maxThroughputPercentage: 60 }, + ], + throughputBucketsBaseline: [ + { id: 1, maxThroughputPercentage: 40 }, + { id: 2, maxThroughputPercentage: 50 }, + ], + onBucketsChange: mockOnBucketsChange, + onSaveableChange: mockOnSaveableChange, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("renders the correct number of buckets", () => { + render(); + expect(screen.getAllByText(/Group \d+/)).toHaveLength(5); + }); + + it("renders buckets in the correct order even if input is unordered", () => { + const unorderedBuckets = [ + { id: 2, maxThroughputPercentage: 60 }, + { id: 1, maxThroughputPercentage: 50 }, + ]; + render(); + + const bucketLabels = screen.getAllByText(/Group \d+/).map((el) => el.textContent); + expect(bucketLabels).toEqual(["Group 1 (Data Explorer Query Bucket)", "Group 2", "Group 3", "Group 4", "Group 5"]); + }); + + it("renders all provided buckets even if they exceed the max default bucket count", () => { + const oversizedBuckets = [ + { id: 1, maxThroughputPercentage: 50 }, + { id: 2, maxThroughputPercentage: 60 }, + { id: 3, maxThroughputPercentage: 70 }, + { id: 4, maxThroughputPercentage: 80 }, + { id: 5, maxThroughputPercentage: 90 }, + { id: 6, maxThroughputPercentage: 100 }, + { id: 7, maxThroughputPercentage: 40 }, + ]; + + render(); + + expect(screen.getAllByText(/Group \d+/)).toHaveLength(7); + + expect(screen.getByDisplayValue("50")).toBeInTheDocument(); + expect(screen.getByDisplayValue("60")).toBeInTheDocument(); + expect(screen.getByDisplayValue("70")).toBeInTheDocument(); + expect(screen.getByDisplayValue("80")).toBeInTheDocument(); + expect(screen.getByDisplayValue("90")).toBeInTheDocument(); + expect(screen.getByDisplayValue("100")).toBeInTheDocument(); + expect(screen.getByDisplayValue("40")).toBeInTheDocument(); + }); + + it("calls onBucketsChange when a bucket value changes", () => { + render(); + const input = screen.getByDisplayValue("50"); + fireEvent.change(input, { target: { value: "70" } }); + + expect(mockOnBucketsChange).toHaveBeenCalledWith([ + { id: 1, maxThroughputPercentage: 70 }, + { id: 2, maxThroughputPercentage: 60 }, + { id: 3, maxThroughputPercentage: 100 }, + { id: 4, maxThroughputPercentage: 100 }, + { id: 5, maxThroughputPercentage: 100 }, + ]); + }); + + it("triggers onSaveableChange when values change", () => { + render(); + const input = screen.getByDisplayValue("50"); + fireEvent.change(input, { target: { value: "80" } }); + + expect(mockOnSaveableChange).toHaveBeenCalledWith(true); + }); + + it("updates state consistently after multiple changes to different buckets", () => { + render(); + + const input1 = screen.getByDisplayValue("50"); + fireEvent.change(input1, { target: { value: "70" } }); + + const input2 = screen.getByDisplayValue("60"); + fireEvent.change(input2, { target: { value: "80" } }); + + expect(mockOnBucketsChange).toHaveBeenCalledWith([ + { id: 1, maxThroughputPercentage: 70 }, + { id: 2, maxThroughputPercentage: 80 }, + { id: 3, maxThroughputPercentage: 100 }, + { id: 4, maxThroughputPercentage: 100 }, + { id: 5, maxThroughputPercentage: 100 }, + ]); + }); + + it("resets to baseline when currentBuckets are reset", () => { + const { rerender } = render(); + const input1 = screen.getByDisplayValue("50"); + fireEvent.change(input1, { target: { value: "70" } }); + + rerender(); + + expect(screen.getByDisplayValue("40")).toBeInTheDocument(); + expect(screen.getByDisplayValue("50")).toBeInTheDocument(); + }); + + it("does not call onBucketsChange when value remains unchanged", () => { + render(); + const input = screen.getByDisplayValue("50"); + fireEvent.change(input, { target: { value: "50" } }); + + expect(mockOnBucketsChange).not.toHaveBeenCalled(); + }); + + it("disables input and slider when maxThroughputPercentage is 100", () => { + render( + , + ); + + const disabledInputs = screen.getAllByDisplayValue("100"); + expect(disabledInputs.length).toBeGreaterThan(0); + expect(disabledInputs[0]).toBeDisabled(); + + const sliders = screen.getAllByRole("slider"); + expect(sliders.length).toBeGreaterThan(0); + expect(sliders[0]).toHaveAttribute("aria-disabled", "true"); + expect(sliders[1]).toHaveAttribute("aria-disabled", "false"); + }); + + it("toggles bucket value between 50 and 100 with switch", () => { + render(); + const toggles = screen.getAllByRole("switch"); + + fireEvent.click(toggles[0]); + + expect(mockOnBucketsChange).toHaveBeenCalledWith([ + { id: 1, maxThroughputPercentage: 100 }, + { id: 2, maxThroughputPercentage: 60 }, + { id: 3, maxThroughputPercentage: 100 }, + { id: 4, maxThroughputPercentage: 100 }, + { id: 5, maxThroughputPercentage: 100 }, + ]); + + fireEvent.click(toggles[0]); + + expect(mockOnBucketsChange).toHaveBeenCalledWith([ + { id: 1, maxThroughputPercentage: 50 }, + { id: 2, maxThroughputPercentage: 60 }, + { id: 3, maxThroughputPercentage: 100 }, + { id: 4, maxThroughputPercentage: 100 }, + { id: 5, maxThroughputPercentage: 100 }, + ]); + }); + + it("ensures default buckets are used when no buckets are provided", () => { + render(); + expect(screen.getAllByText(/Group \d+/)).toHaveLength(5); + expect(screen.getAllByDisplayValue("100")).toHaveLength(5); + }); +}); diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent.tsx new file mode 100644 index 000000000..a9408b1e4 --- /dev/null +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent.tsx @@ -0,0 +1,105 @@ +import { Label, Slider, Stack, TextField, Toggle } from "@fluentui/react"; +import { ThroughputBucket } from "Contracts/DataModels"; +import React, { FC, useEffect, useState } from "react"; +import { isDirty } from "../../SettingsUtils"; + +const MAX_BUCKET_SIZES = 5; + +const DEFAULT_BUCKETS = Array.from({ length: MAX_BUCKET_SIZES }, (_, i) => ({ + id: i + 1, + maxThroughputPercentage: 100, +})); + +export interface ThroughputBucketsComponentProps { + currentBuckets: ThroughputBucket[]; + throughputBucketsBaseline: ThroughputBucket[]; + onBucketsChange: (updatedBuckets: ThroughputBucket[]) => void; + onSaveableChange: (isSaveable: boolean) => void; +} + +export const ThroughputBucketsComponent: FC = ({ + currentBuckets, + throughputBucketsBaseline, + onBucketsChange, + onSaveableChange, +}) => { + const getThroughputBuckets = (buckets: ThroughputBucket[]): ThroughputBucket[] => { + if (!buckets || buckets.length === 0) { + return DEFAULT_BUCKETS; + } + const maxBuckets = Math.max(DEFAULT_BUCKETS.length, buckets.length); + const adjustedDefaultBuckets = Array.from({ length: maxBuckets }, (_, i) => ({ + id: i + 1, + maxThroughputPercentage: 100, + })); + + return adjustedDefaultBuckets.map( + (defaultBucket) => buckets?.find((bucket) => bucket.id === defaultBucket.id) || defaultBucket, + ); + }; + + const [throughputBuckets, setThroughputBuckets] = useState(getThroughputBuckets(currentBuckets)); + + useEffect(() => { + setThroughputBuckets(getThroughputBuckets(currentBuckets)); + onSaveableChange(false); + }, [currentBuckets]); + + useEffect(() => { + const isChanged = isDirty(throughputBuckets, getThroughputBuckets(throughputBucketsBaseline)); + onSaveableChange(isChanged); + }, [throughputBuckets]); + + const handleBucketChange = (id: number, newValue: number) => { + const updatedBuckets = throughputBuckets.map((bucket) => + bucket.id === id ? { ...bucket, maxThroughputPercentage: newValue } : bucket, + ); + setThroughputBuckets(updatedBuckets); + const settingsChanged = isDirty(updatedBuckets, throughputBuckets); + settingsChanged && onBucketsChange(updatedBuckets); + }; + + const onToggle = (id: number, checked: boolean) => { + handleBucketChange(id, checked ? 50 : 100); + }; + + return ( + + + + {throughputBuckets?.map((bucket) => ( + + handleBucketChange(bucket.id, newValue)} + showValue={false} + label={`Group ${bucket.id}${bucket.id === 1 ? " (Data Explorer Query Bucket)" : ""}`} + styles={{ root: { flex: 2, maxWidth: 400 } }} + disabled={bucket.maxThroughputPercentage === 100} + /> + handleBucketChange(bucket.id, parseInt(newValue || "0", 10))} + type="number" + suffix="%" + styles={{ + fieldGroup: { width: 80 }, + }} + disabled={bucket.maxThroughputPercentage === 100} + /> + onToggle(bucket.id, checked)} + styles={{ root: { marginBottom: 0 }, text: { fontSize: 12 } }} + > + + ))} + + + ); +}; diff --git a/src/Explorer/Controls/Settings/SettingsUtils.tsx b/src/Explorer/Controls/Settings/SettingsUtils.tsx index fa8360b9b..900ad6ab0 100644 --- a/src/Explorer/Controls/Settings/SettingsUtils.tsx +++ b/src/Explorer/Controls/Settings/SettingsUtils.tsx @@ -11,7 +11,8 @@ export type isDirtyTypes = | DataModels.IndexingPolicy | DataModels.ComputedProperties | DataModels.VectorEmbedding[] - | DataModels.FullTextPolicy; + | DataModels.FullTextPolicy + | DataModels.ThroughputBucket[]; export const TtlOff = "off"; export const TtlOn = "on"; export const TtlOnNoDefault = "on-nodefault"; @@ -55,6 +56,7 @@ export enum SettingsV2TabTypes { PartitionKeyTab, ComputedPropertiesTab, ContainerVectorPolicyTab, + ThroughputBucketsTab, } export enum ContainerPolicyTabTypes { @@ -167,6 +169,8 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => { return "Computed Properties"; case SettingsV2TabTypes.ContainerVectorPolicyTab: return "Container Policies"; + case SettingsV2TabTypes.ThroughputBucketsTab: + return "Throughput Buckets"; default: throw new Error(`Unknown tab ${tab}`); } diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 8740ee616..fdef1076e 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -1127,7 +1127,7 @@ export default class Explorer { await this.initNotebooks(userContext.databaseAccount); } - await this.refreshSampleData(); + this.refreshSampleData(); } public async configureCopilot(): Promise { @@ -1152,26 +1152,27 @@ export default class Explorer { .setCopilotSampleDBEnabled(copilotEnabled && copilotUserDBEnabled && copilotSampleDBEnabled); } - public async refreshSampleData(): Promise { - try { - if (!userContext.sampleDataConnectionInfo) { - return; - } - const collection: DataModels.Collection = await readSampleCollection(); - if (!collection) { - return; - } - - const databaseId = userContext.sampleDataConnectionInfo?.databaseId; - if (!databaseId) { - return; - } - - const sampleDataResourceTokenCollection = new ResourceTokenCollection(this, databaseId, collection, true); - useDatabases.setState({ sampleDataResourceTokenCollection }); - } catch (error) { - Logger.logError(getErrorMessage(error), "Explorer"); + public refreshSampleData(): void { + if (!userContext.sampleDataConnectionInfo) { return; } + + const databaseId = userContext.sampleDataConnectionInfo?.databaseId; + if (!databaseId) { + return; + } + + readSampleCollection() + .then((collection: DataModels.Collection) => { + if (!collection) { + return; + } + + const sampleDataResourceTokenCollection = new ResourceTokenCollection(this, databaseId, collection, true); + useDatabases.setState({ sampleDataResourceTokenCollection }); + }) + .catch((error) => { + Logger.logError(getErrorMessage(error), "Explorer/refreshSampleData"); + }); } } diff --git a/src/Explorer/Notebook/useNotebook.ts b/src/Explorer/Notebook/useNotebook.ts index a0f6efbf0..64acb2e3f 100644 --- a/src/Explorer/Notebook/useNotebook.ts +++ b/src/Explorer/Notebook/useNotebook.ts @@ -1,6 +1,5 @@ import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility"; import { PhoenixClient } from "Phoenix/PhoenixClient"; -import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils"; import { cloneDeep } from "lodash"; import create, { UseStore } from "zustand"; import { AuthType } from "../../AuthType"; @@ -128,9 +127,7 @@ export const useNotebook: UseStore = create((set, get) => ({ userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo" ? databaseAccount?.location : databaseAccount?.properties?.writeLocations?.[0]?.locationName.toLowerCase(); - const disallowedLocationsUri: string = useNewPortalBackendEndpoint(Constants.BackendApi.DisallowedLocations) - ? `${configContext.PORTAL_BACKEND_ENDPOINT}/api/disallowedlocations` - : `${configContext.BACKEND_ENDPOINT}/api/disallowedLocations`; + const disallowedLocationsUri: string = `${configContext.PORTAL_BACKEND_ENDPOINT}/api/disallowedlocations`; const authorizationHeader = getAuthorizationHeader(); try { const response = await fetch(disallowedLocationsUri, { diff --git a/src/Explorer/QueryCopilot/Shared/QueryCopilotClient.ts b/src/Explorer/QueryCopilot/Shared/QueryCopilotClient.ts index 5d187be85..ede7f1933 100644 --- a/src/Explorer/QueryCopilot/Shared/QueryCopilotClient.ts +++ b/src/Explorer/QueryCopilot/Shared/QueryCopilotClient.ts @@ -1,7 +1,6 @@ import { FeedOptions } from "@azure/cosmos"; import { Areas, - BackendApi, ConnectionStatusType, ContainerStatusType, HttpStatusCodes, @@ -32,7 +31,6 @@ import { Action } from "Shared/Telemetry/TelemetryConstants"; import { traceFailure, traceStart, traceSuccess } from "Shared/Telemetry/TelemetryProcessor"; import { userContext } from "UserContext"; import { getAuthorizationHeader } from "Utils/AuthorizationUtils"; -import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils"; import { queryPagesUntilContentPresent } from "Utils/QueryUtils"; import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot"; import { useTabs } from "hooks/useTabs"; @@ -82,9 +80,7 @@ export const isCopilotFeatureRegistered = async (subscriptionId: string): Promis }; export const getCopilotEnabled = async (): Promise => { - const backendEndpoint: string = useNewPortalBackendEndpoint(BackendApi.PortalSettings) - ? configContext.PORTAL_BACKEND_ENDPOINT - : configContext.BACKEND_ENDPOINT; + const backendEndpoint: string = configContext.PORTAL_BACKEND_ENDPOINT; const url = `${backendEndpoint}/api/portalsettings/querycopilot`; const authorizationHeader: AuthorizationTokenHeaderMetadata = getAuthorizationHeader(); diff --git a/src/Explorer/Tables/TableDataClient.ts b/src/Explorer/Tables/TableDataClient.ts index 8e652d6b0..a842e9986 100644 --- a/src/Explorer/Tables/TableDataClient.ts +++ b/src/Explorer/Tables/TableDataClient.ts @@ -3,7 +3,7 @@ import * as ko from "knockout"; import Q from "q"; import { AuthType } from "../../AuthType"; import * as Constants from "../../Common/Constants"; -import { CassandraProxyAPIs, CassandraProxyEndpoints } from "../../Common/Constants"; +import { CassandraProxyAPIs } from "../../Common/Constants"; import { handleError } from "../../Common/ErrorHandlingUtils"; import * as HeadersUtility from "../../Common/HeadersUtility"; import { createDocument } from "../../Common/dataAccess/createDocument"; @@ -264,9 +264,6 @@ export class CassandraAPIDataClient extends TableDataClient { shouldNotify?: boolean, paginationToken?: string, ): Promise { - if (!this.useCassandraProxyEndpoint("postQuery")) { - return this.queryDocuments_ToBeDeprecated(collection, query, shouldNotify, paginationToken); - } const clearMessage = shouldNotify && NotificationConsoleUtils.logConsoleProgress(`Querying rows for table ${collection.id()}`); try { @@ -309,55 +306,6 @@ export class CassandraAPIDataClient extends TableDataClient { } } - public async queryDocuments_ToBeDeprecated( - collection: ViewModels.Collection, - query: string, - shouldNotify?: boolean, - paginationToken?: string, - ): Promise { - const clearMessage = - shouldNotify && NotificationConsoleUtils.logConsoleProgress(`Querying rows for table ${collection.id()}`); - try { - const { authType, databaseAccount } = userContext; - const apiEndpoint: string = - authType === AuthType.EncryptedToken - ? Constants.CassandraBackend.guestQueryApi - : Constants.CassandraBackend.queryApi; - const data: any = await $.ajax(`${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`, { - type: "POST", - data: { - accountName: databaseAccount?.name, - cassandraEndpoint: this.trimCassandraEndpoint(databaseAccount?.properties.cassandraEndpoint), - resourceId: databaseAccount?.id, - keyspaceId: collection.databaseId, - tableId: collection.id(), - query, - paginationToken, - }, - beforeSend: this.setAuthorizationHeader as any, - cache: false, - }); - shouldNotify && - NotificationConsoleUtils.logConsoleInfo( - `Successfully fetched ${data.result.length} rows for table ${collection.id()}`, - ); - return { - Results: data.result, - ContinuationToken: data.paginationToken, - }; - } catch (error) { - shouldNotify && - handleError( - error, - "QueryDocuments_ToBeDeprecated_Cassandra", - `Failed to query rows for table ${collection.id()}`, - ); - throw error; - } finally { - clearMessage?.(); - } - } - public async deleteDocuments( collection: ViewModels.Collection, entitiesToDelete: Entities.ITableEntity[], @@ -471,10 +419,6 @@ export class CassandraAPIDataClient extends TableDataClient { } public getTableKeys(collection: ViewModels.Collection): Q.Promise { - if (!this.useCassandraProxyEndpoint("getKeys")) { - return this.getTableKeys_ToBeDeprecated(collection); - } - if (!!collection.cassandraKeys) { return Q.resolve(collection.cassandraKeys); } @@ -515,52 +459,7 @@ export class CassandraAPIDataClient extends TableDataClient { return deferred.promise; } - public getTableKeys_ToBeDeprecated(collection: ViewModels.Collection): Q.Promise { - if (!!collection.cassandraKeys) { - return Q.resolve(collection.cassandraKeys); - } - const clearInProgressMessage = logConsoleProgress(`Fetching keys for table ${collection.id()}`); - const { authType, databaseAccount } = userContext; - const apiEndpoint: string = - authType === AuthType.EncryptedToken - ? Constants.CassandraBackend.guestKeysApi - : Constants.CassandraBackend.keysApi; - let endpoint = `${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`; - const deferred = Q.defer(); - - $.ajax(endpoint, { - type: "POST", - data: { - accountName: databaseAccount?.name, - cassandraEndpoint: this.trimCassandraEndpoint(databaseAccount?.properties.cassandraEndpoint), - resourceId: databaseAccount?.id, - keyspaceId: collection.databaseId, - tableId: collection.id(), - }, - beforeSend: this.setAuthorizationHeader as any, - cache: false, - }) - .then( - (data: CassandraTableKeys) => { - collection.cassandraKeys = data; - logConsoleInfo(`Successfully fetched keys for table ${collection.id()}`); - deferred.resolve(data); - }, - (error: any) => { - const errorText = error.responseJSON?.message ?? JSON.stringify(error); - handleError(errorText, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`); - deferred.reject(errorText); - }, - ) - .done(clearInProgressMessage); - return deferred.promise; - } - public getTableSchema(collection: ViewModels.Collection): Q.Promise { - if (!this.useCassandraProxyEndpoint("getSchema")) { - return this.getTableSchema_ToBeDeprecated(collection); - } - if (!!collection.cassandraSchema) { return Q.resolve(collection.cassandraSchema); } @@ -602,52 +501,7 @@ export class CassandraAPIDataClient extends TableDataClient { return deferred.promise; } - public getTableSchema_ToBeDeprecated(collection: ViewModels.Collection): Q.Promise { - if (!!collection.cassandraSchema) { - return Q.resolve(collection.cassandraSchema); - } - const clearInProgressMessage = logConsoleProgress(`Fetching schema for table ${collection.id()}`); - const { databaseAccount, authType } = userContext; - const apiEndpoint: string = - authType === AuthType.EncryptedToken - ? Constants.CassandraBackend.guestSchemaApi - : Constants.CassandraBackend.schemaApi; - let endpoint = `${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`; - const deferred = Q.defer(); - - $.ajax(endpoint, { - type: "POST", - data: { - accountName: databaseAccount?.name, - cassandraEndpoint: this.trimCassandraEndpoint(databaseAccount?.properties.cassandraEndpoint), - resourceId: databaseAccount?.id, - keyspaceId: collection.databaseId, - tableId: collection.id(), - }, - beforeSend: this.setAuthorizationHeader as any, - cache: false, - }) - .then( - (data: any) => { - collection.cassandraSchema = data.columns; - logConsoleInfo(`Successfully fetched schema for table ${collection.id()}`); - deferred.resolve(data.columns); - }, - (error: any) => { - const errorText = error.responseJSON?.message ?? JSON.stringify(error); - handleError(errorText, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`); - deferred.reject(errorText); - }, - ) - .done(clearInProgressMessage); - return deferred.promise; - } - private createOrDeleteQuery(cassandraEndpoint: string, resourceId: string, query: string): Q.Promise { - if (!this.useCassandraProxyEndpoint("createOrDelete")) { - return this.createOrDeleteQuery_ToBeDeprecated(cassandraEndpoint, resourceId, query); - } - const deferred = Q.defer(); const { authType, databaseAccount } = userContext; const apiEndpoint: string = @@ -677,38 +531,6 @@ export class CassandraAPIDataClient extends TableDataClient { return deferred.promise; } - private createOrDeleteQuery_ToBeDeprecated( - cassandraEndpoint: string, - resourceId: string, - query: string, - ): Q.Promise { - const deferred = Q.defer(); - const { authType, databaseAccount } = userContext; - const apiEndpoint: string = - authType === AuthType.EncryptedToken - ? Constants.CassandraBackend.guestCreateOrDeleteApi - : Constants.CassandraBackend.createOrDeleteApi; - $.ajax(`${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`, { - type: "POST", - data: { - accountName: databaseAccount?.name, - cassandraEndpoint: this.trimCassandraEndpoint(cassandraEndpoint), - resourceId: resourceId, - query: query, - }, - beforeSend: this.setAuthorizationHeader as any, - cache: false, - }).then( - (data: any) => { - deferred.resolve(); - }, - (reason) => { - deferred.reject(reason); - }, - ); - return deferred.promise; - } - private trimCassandraEndpoint(cassandraEndpoint: string): string { if (!cassandraEndpoint) { return cassandraEndpoint; @@ -747,23 +569,4 @@ export class CassandraAPIDataClient extends TableDataClient { private getCassandraPartitionKeyProperty(collection: ViewModels.Collection): string { return collection.cassandraKeys.partitionKeys[0].property; } - - private useCassandraProxyEndpoint(api: string): boolean { - const activeCassandraProxyEndpoints: string[] = [ - CassandraProxyEndpoints.Development, - CassandraProxyEndpoints.Mpac, - CassandraProxyEndpoints.Prod, - CassandraProxyEndpoints.Fairfax, - CassandraProxyEndpoints.Mooncake, - ]; - - if (configContext.globallyEnabledCassandraAPIs.includes(api)) { - return true; - } - - return ( - configContext.NEW_CASSANDRA_APIS?.includes(api) && - activeCassandraProxyEndpoints.includes(configContext.CASSANDRA_PROXY_ENDPOINT) - ); - } } diff --git a/src/Explorer/Tabs/DocumentsTabV2/DocumentsTabV2.tsx b/src/Explorer/Tabs/DocumentsTabV2/DocumentsTabV2.tsx index 2548d582b..2cef0f663 100644 --- a/src/Explorer/Tabs/DocumentsTabV2/DocumentsTabV2.tsx +++ b/src/Explorer/Tabs/DocumentsTabV2/DocumentsTabV2.tsx @@ -1150,27 +1150,16 @@ export const DocumentsTabComponent: React.FunctionComponent [toDeleteDocumentIds[0]]); - // ---------------------------------------------------------------------------------------------------- - } else { - deletePromise = MongoProxyClient.deleteDocuments( - _collection.databaseId, - _collection as ViewModels.Collection, - toDeleteDocumentIds, - ).then(({ deletedCount, isAcknowledged }) => { - if (deletedCount === toDeleteDocumentIds.length && isAcknowledged) { - return toDeleteDocumentIds; - } - throw new Error(`Delete failed with deletedCount: ${deletedCount} and isAcknowledged: ${isAcknowledged}`); - }); - } + deletePromise = MongoProxyClient.deleteDocuments( + _collection.databaseId, + _collection as ViewModels.Collection, + toDeleteDocumentIds, + ).then(({ deletedCount, isAcknowledged }) => { + if (deletedCount === toDeleteDocumentIds.length && isAcknowledged) { + return toDeleteDocumentIds; + } + throw new Error(`Delete failed with deletedCount: ${deletedCount} and isAcknowledged: ${isAcknowledged}`); + }); } return deletePromise @@ -2054,11 +2043,8 @@ export const DocumentsTabComponent: React.FunctionComponent { diff --git a/src/Explorer/Tabs/MongoShellTab/MongoShellTabComponent.tsx b/src/Explorer/Tabs/MongoShellTab/MongoShellTabComponent.tsx index 7bba85296..e0d9d6d29 100644 --- a/src/Explorer/Tabs/MongoShellTab/MongoShellTabComponent.tsx +++ b/src/Explorer/Tabs/MongoShellTab/MongoShellTabComponent.tsx @@ -1,6 +1,4 @@ -import { useMongoProxyEndpoint } from "Common/MongoProxyClient"; import React, { Component } from "react"; -import * as Constants from "../../../Common/Constants"; import { configContext } from "../../../ConfigContext"; import * as ViewModels from "../../../Contracts/ViewModels"; import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; @@ -50,15 +48,13 @@ export default class MongoShellTabComponent extends Component< IMongoShellTabComponentStates > { private _logTraces: Map; - private _useMongoProxyEndpoint: boolean; constructor(props: IMongoShellTabComponentProps) { super(props); this._logTraces = new Map(); - this._useMongoProxyEndpoint = useMongoProxyEndpoint(Constants.MongoProxyApi.LegacyMongoShell); this.state = { - url: getMongoShellUrl(this._useMongoProxyEndpoint), + url: getMongoShellUrl(), }; props.onMongoShellTabAccessor({ @@ -113,17 +109,9 @@ export default class MongoShellTabComponent extends Component< const resourceId = databaseAccount?.id; const accountName = databaseAccount?.name; const documentEndpoint = databaseAccount?.properties.mongoEndpoint || databaseAccount?.properties.documentEndpoint; - const mongoEndpoint = - documentEndpoint.substr( - Constants.MongoDBAccounts.protocol.length + 3, - documentEndpoint.length - - (Constants.MongoDBAccounts.protocol.length + 2 + Constants.MongoDBAccounts.defaultPort.length), - ) + Constants.MongoDBAccounts.defaultPort.toString(); const databaseId = this.props.collection.databaseId; const collectionId = this.props.collection.id(); - const apiEndpoint = this._useMongoProxyEndpoint - ? configContext.MONGO_PROXY_ENDPOINT - : configContext.BACKEND_ENDPOINT; + const apiEndpoint = configContext.MONGO_PROXY_ENDPOINT; const encryptedAuthToken: string = userContext.accessToken; shellIframe.contentWindow.postMessage( @@ -132,7 +120,7 @@ export default class MongoShellTabComponent extends Component< data: { resourceId: resourceId, accountName: accountName, - mongoEndpoint: this._useMongoProxyEndpoint ? documentEndpoint : mongoEndpoint, + mongoEndpoint: documentEndpoint, authorization: authorization, databaseId: databaseId, collectionId: collectionId, diff --git a/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.test.ts b/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.test.ts index 8b16816ab..924b2eb13 100644 --- a/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.test.ts +++ b/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.test.ts @@ -2,8 +2,6 @@ import { Platform, resetConfigContext, updateConfigContext } from "../../../Conf import { updateUserContext, userContext } from "../../../UserContext"; import { getMongoShellUrl } from "./getMongoShellUrl"; -const mongoBackendEndpoint = "https://localhost:1234"; - describe("getMongoShellUrl", () => { let queryString = ""; @@ -11,7 +9,6 @@ describe("getMongoShellUrl", () => { resetConfigContext(); updateConfigContext({ - BACKEND_ENDPOINT: mongoBackendEndpoint, platform: Platform.Hosted, }); @@ -37,12 +34,7 @@ describe("getMongoShellUrl", () => { queryString = `resourceId=${userContext.databaseAccount.id}&accountName=${userContext.databaseAccount.name}&mongoEndpoint=${userContext.databaseAccount.properties.documentEndpoint}`; }); - it("should return /indexv2.html by default", () => { - expect(getMongoShellUrl().toString()).toContain(`/indexv2.html?${queryString}`); - }); - - it("should return /index.html when useMongoProxyEndpoint is true", () => { - const useMongoProxyEndpoint: boolean = true; - expect(getMongoShellUrl(useMongoProxyEndpoint).toString()).toContain(`/index.html?${queryString}`); + it("should return /index.html by default", () => { + expect(getMongoShellUrl().toString()).toContain(`/index.html?${queryString}`); }); }); diff --git a/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.ts b/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.ts index a3b49b373..36f47ce74 100644 --- a/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.ts +++ b/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.ts @@ -1,11 +1,11 @@ import { userContext } from "../../../UserContext"; -export function getMongoShellUrl(useMongoProxyEndpoint?: boolean): string { +export function getMongoShellUrl(): string { const { databaseAccount: account } = userContext; const resourceId = account?.id; const accountName = account?.name; const mongoEndpoint = account?.properties?.mongoEndpoint || account?.properties?.documentEndpoint; const queryString = `resourceId=${resourceId}&accountName=${accountName}&mongoEndpoint=${mongoEndpoint}`; - return useMongoProxyEndpoint ? `/mongoshell/index.html?${queryString}` : `/mongoshell/indexv2.html?${queryString}`; + return `/mongoshell/index.html?${queryString}`; } diff --git a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx index 2347e0822..821aebfe5 100644 --- a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx +++ b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx @@ -375,6 +375,7 @@ class QueryTabComponentImpl extends React.Component await queryDocumentsPage( this.props.collection && this.props.collection.id(), diff --git a/src/Explorer/Tabs/Tabs.tsx b/src/Explorer/Tabs/Tabs.tsx index e451dab69..378b3ec1c 100644 --- a/src/Explorer/Tabs/Tabs.tsx +++ b/src/Explorer/Tabs/Tabs.tsx @@ -1,7 +1,3 @@ -import { IMessageBarStyles, MessageBar, MessageBarType } from "@fluentui/react"; -import { CassandraProxyEndpoints, MongoProxyEndpoints } from "Common/Constants"; -import { configContext } from "ConfigContext"; -import { IpRule } from "Contracts/DataModels"; import { CollectionTabKind } from "Contracts/ViewModels"; import Explorer from "Explorer/Explorer"; import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter"; @@ -12,10 +8,8 @@ import { PostgresConnectTab } from "Explorer/Tabs/PostgresConnectTab"; import { QuickstartTab } from "Explorer/Tabs/QuickstartTab"; import { VcoreMongoConnectTab } from "Explorer/Tabs/VCoreMongoConnectTab"; import { VcoreMongoQuickstartTab } from "Explorer/Tabs/VCoreMongoQuickstartTab"; -import { LayoutConstants } from "Explorer/Theme/ThemeUtil"; import { KeyboardAction, KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts"; import { userContext } from "UserContext"; -import { CassandraProxyOutboundIPs, MongoProxyOutboundIPs, PortalBackendIPs } from "Utils/EndpointUtils"; import { useTeachingBubble } from "hooks/useTeachingBubble"; import ko from "knockout"; import React, { MutableRefObject, useEffect, useRef, useState } from "react"; @@ -34,10 +28,6 @@ interface TabsProps { export const Tabs = ({ explorer }: TabsProps): JSX.Element => { const { openedTabs, openedReactTabs, activeTab, activeReactTab } = useTabs(); - const [ - showMongoAndCassandraProxiesNetworkSettingsWarningState, - setShowMongoAndCassandraProxiesNetworkSettingsWarningState, - ] = useState(showMongoAndCassandraProxiesNetworkSettingsWarning()); const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.TABS); useEffect(() => { @@ -48,28 +38,8 @@ export const Tabs = ({ explorer }: TabsProps): JSX.Element => { }); }, [setKeyboardHandlers]); - const defaultMessageBarStyles: IMessageBarStyles = { - root: { - height: `${LayoutConstants.rowHeight}px`, - overflow: "hidden", - flexDirection: "row", - }, - }; - return (
- {showMongoAndCassandraProxiesNetworkSettingsWarningState && ( - { - setShowMongoAndCassandraProxiesNetworkSettingsWarningState(false); - }} - > - {`We have migrated our middleware to new infrastructure. To avoid issues with Data Explorer access, please - re-enable "Allow access from Azure Portal" on the Networking blade for your account.`} - - )}