From 7893b89bf7f82e4316260765caaacbb2c992b767 Mon Sep 17 00:00:00 2001 From: Laurent Nguyen Date: Thu, 6 Feb 2025 21:58:38 +0100 Subject: [PATCH 01/17] Do not open first container if a tab is already open (#2045) Co-authored-by: Laurent Nguyen --- src/hooks/useKnockoutExplorer.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/hooks/useKnockoutExplorer.ts b/src/hooks/useKnockoutExplorer.ts index 3eacd9577..8fc07ae4b 100644 --- a/src/hooks/useKnockoutExplorer.ts +++ b/src/hooks/useKnockoutExplorer.ts @@ -181,6 +181,11 @@ async function configureFabric(): Promise { } const openFirstContainer = async (explorer: Explorer, databaseName: string, collectionName?: string) => { + if (useTabs.getState().openedTabs.length > 0) { + // Don't open any tabs if there are already tabs open + return; + } + // Expand database first databaseName = sessionStorage.getItem("openDatabaseName") ?? databaseName; const database = useDatabases.getState().databases.find((db) => db.id() === databaseName); From 76d49d86d40370191f9edc0772218850cdb473fe Mon Sep 17 00:00:00 2001 From: Nishtha Ahuja <45535788+nishthaAhujaa@users.noreply.github.com> Date: Mon, 10 Feb 2025 11:52:56 +0530 Subject: [PATCH 02/17] Added emulator checks in settings pane fields (#2041) * added emulator checks * created macro * conditions as const --------- Co-authored-by: Nishtha Ahuja --- .../Panes/SettingsPane/SettingsPane.tsx | 130 ++++++++++-------- 1 file changed, 70 insertions(+), 60 deletions(-) diff --git a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx index b5f1e1efd..a19c89be2 100644 --- a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx +++ b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx @@ -174,15 +174,26 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ const styles = useStyles(); const explorerVersion = configContext.gitSha; + const isEmulator = configContext.platform === Platform.Emulator; const shouldShowQueryPageOptions = userContext.apiType === "SQL"; - const shouldShowGraphAutoVizOption = userContext.apiType === "Gremlin"; - const shouldShowCrossPartitionOption = userContext.apiType !== "Gremlin"; - const shouldShowParallelismOption = userContext.apiType !== "Gremlin"; - const shouldShowPriorityLevelOption = PriorityBasedExecutionUtils.isFeatureEnabled(); + const showRetrySettings = + (userContext.apiType === "SQL" || userContext.apiType === "Tables" || userContext.apiType === "Gremlin") && + !isEmulator; + const shouldShowGraphAutoVizOption = userContext.apiType === "Gremlin" && !isEmulator; + const shouldShowCrossPartitionOption = userContext.apiType !== "Gremlin" && !isEmulator; + const shouldShowParallelismOption = userContext.apiType !== "Gremlin" && !isEmulator; + const showEnableEntraIdRbac = + userContext.apiType === "SQL" && + userContext.authType === AuthType.AAD && + configContext.platform !== Platform.Fabric && + !isEmulator; + const shouldShowPriorityLevelOption = PriorityBasedExecutionUtils.isFeatureEnabled() && !isEmulator; const shouldShowCopilotSampleDBOption = userContext.apiType === "SQL" && useQueryCopilot.getState().copilotEnabled && - useDatabases.getState().sampleDataResourceTokenCollection; + useDatabases.getState().sampleDataResourceTokenCollection && + !isEmulator; + const handlerOnSubmit = async () => { setIsExecuting(true); @@ -541,39 +552,37 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ )} - {userContext.apiType === "SQL" && - userContext.authType === AuthType.AAD && - configContext.platform !== Platform.Fabric && ( - - -
Enable Entra ID RBAC
-
- -
-
- Choose Automatic to enable Entra ID RBAC automatically. True/False to force enable/disable Entra - ID RBAC. - - {" "} - Learn more{" "} - -
- + {showEnableEntraIdRbac && ( + + +
Enable Entra ID RBAC
+
+ +
+
+ Choose Automatic to enable Entra ID RBAC automatically. True/False to force enable/disable Entra ID + RBAC. + + {" "} + Learn more{" "} +
- - - )} - {userContext.apiType === "SQL" && ( + +
+
+
+ )} + {userContext.apiType === "SQL" && !isEmulator && ( <> @@ -671,7 +680,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ )} - {(userContext.apiType === "SQL" || userContext.apiType === "Tables" || userContext.apiType === "Gremlin") && ( + {showRetrySettings && (
Retry Settings
@@ -744,29 +753,30 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
)} - - - -
Enable container pagination
-
- -
-
- Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order. + {!isEmulator && ( + + +
Enable container pagination
+
+ +
+
+ Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order. +
+ setContainerPaginationEnabled(!containerPaginationEnabled)} + label="Enable container pagination" + />
- setContainerPaginationEnabled(!containerPaginationEnabled)} - label="Enable container pagination" - /> -
- - + + + )} {shouldShowCrossPartitionOption && ( From c2b98c3e236c1438cb85817e7a0d5df576ae53f8 Mon Sep 17 00:00:00 2001 From: jawelton74 <103591340+jawelton74@users.noreply.github.com> Date: Mon, 10 Feb 2025 08:48:26 -0800 Subject: [PATCH 03/17] Modify E2E cleanup script to use @azure/identity for AZ credentials. (#2051) --- utils/cleanupDBs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/cleanupDBs.js b/utils/cleanupDBs.js index be1e1dc0d..3c89a8c5c 100644 --- a/utils/cleanupDBs.js +++ b/utils/cleanupDBs.js @@ -1,4 +1,4 @@ -const msRestNodeAuth = require("@azure/ms-rest-nodeauth"); +const { AzureCliCredential } = require("@azure/identity"); const { CosmosDBManagementClient } = require("@azure/arm-cosmosdb"); const ms = require("ms"); @@ -16,7 +16,7 @@ function friendlyTime(date) { } async function main() { - const credentials = await msRestNodeAuth.AzureCliCredentials.create(); + const credentials = new AzureCliCredential(); const client = new CosmosDBManagementClient(credentials, subscriptionId); const accounts = await client.databaseAccounts.list(resourceGroupName); for (const account of accounts) { From 9fb006a996571bf030566549956eea095d3dd77e Mon Sep 17 00:00:00 2001 From: jawelton74 <103591340+jawelton74@users.noreply.github.com> Date: Tue, 11 Feb 2025 06:58:44 -0800 Subject: [PATCH 04/17] Restore DisplayNPSSurvey message type enum which was removed in a prior (#2046) change. --- src/Contracts/MessageTypes.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Contracts/MessageTypes.ts b/src/Contracts/MessageTypes.ts index a5952e098..1dd30bb89 100644 --- a/src/Contracts/MessageTypes.ts +++ b/src/Contracts/MessageTypes.ts @@ -41,6 +41,7 @@ export enum MessageTypes { OpenPostgreSQLPasswordReset, OpenPostgresNetworkingBlade, OpenCosmosDBNetworkingBlade, + DisplayNPSSurvey, // unused OpenVCoreMongoNetworkingBlade, OpenVCoreMongoConnectionStringsBlade, GetAuthorizationToken, // unused. Can be removed if the portal uses the same list of enums. From 644f5941ec3da37160866f56591465365e6af898 Mon Sep 17 00:00:00 2001 From: asier-isayas Date: Tue, 11 Feb 2025 17:47:55 -0500 Subject: [PATCH 05/17] Set default throughput based on account's workload type (#2021) * assign default throughput based on workload type * combined common logic * fix unit tests * add tests * update tests * npm run format * Update ci.yml --------- Co-authored-by: Asier Isayas --- .github/workflows/ci.yml | 2 +- src/Common/Constants.ts | 7 ++++ src/Common/DatabaseAccountUtility.test.ts | 34 +++++++++++++++++++ src/Common/DatabaseAccountUtility.ts | 11 ++++++ src/Contracts/DataModels.ts | 3 ++ .../ThroughputInput/ThroughputInput.tsx | 16 +++++---- 6 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 src/Common/DatabaseAccountUtility.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd741b074..56ed46edc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,7 +83,7 @@ jobs: - run: npm ci - run: npm run build:contracts - name: Restore Build Cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: .cache key: ${{ runner.os }}-build-cache diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts index fff625dc7..840849904 100644 --- a/src/Common/Constants.ts +++ b/src/Common/Constants.ts @@ -97,6 +97,12 @@ export enum CapacityMode { Serverless = "Serverless", } +export enum WorkloadType { + Learning = "Learning", + DevelopmentTesting = "Development/Testing", + Production = "Production", + None = "None", +} // flight names returned from the portal are always lowercase export class Flights { public static readonly SettingsV2 = "settingsv2"; @@ -119,6 +125,7 @@ export class AfecFeatures { export class TagNames { public static defaultExperience: string = "defaultExperience"; + public static WorkloadType: string = "hidden-workload-type"; } export class MongoDBAccounts { diff --git a/src/Common/DatabaseAccountUtility.test.ts b/src/Common/DatabaseAccountUtility.test.ts new file mode 100644 index 000000000..0e0f015e8 --- /dev/null +++ b/src/Common/DatabaseAccountUtility.test.ts @@ -0,0 +1,34 @@ +import { WorkloadType } from "Common/Constants"; +import { getWorkloadType } from "Common/DatabaseAccountUtility"; +import { DatabaseAccount, Tags } from "Contracts/DataModels"; +import { updateUserContext } from "UserContext"; + +describe("Database Account Utility", () => { + describe("Workload Type", () => { + beforeEach(() => { + updateUserContext({ + databaseAccount: { + tags: {} as Tags, + } as DatabaseAccount, + }); + }); + + it("Workload Type should return Learning", () => { + updateUserContext({ + databaseAccount: { + tags: { + "hidden-workload-type": WorkloadType.Learning, + } as Tags, + } as DatabaseAccount, + }); + + const workloadType: WorkloadType = getWorkloadType(); + expect(workloadType).toBe(WorkloadType.Learning); + }); + + it("Workload Type should return None", () => { + const workloadType: WorkloadType = getWorkloadType(); + expect(workloadType).toBe(WorkloadType.None); + }); + }); +}); diff --git a/src/Common/DatabaseAccountUtility.ts b/src/Common/DatabaseAccountUtility.ts index 693f581f8..c72d3baf6 100644 --- a/src/Common/DatabaseAccountUtility.ts +++ b/src/Common/DatabaseAccountUtility.ts @@ -1,3 +1,5 @@ +import { TagNames, WorkloadType } from "Common/Constants"; +import { Tags } from "Contracts/DataModels"; import { userContext } from "../UserContext"; function isVirtualNetworkFilterEnabled() { @@ -15,3 +17,12 @@ function isPrivateEndpointConnectionsEnabled() { export function isPublicInternetAccessAllowed(): boolean { return !isVirtualNetworkFilterEnabled() && !isIpRulesEnabled() && !isPrivateEndpointConnectionsEnabled(); } + +export function getWorkloadType(): WorkloadType { + const tags: Tags = userContext?.databaseAccount?.tags; + const workloadType: WorkloadType = tags && (tags[TagNames.WorkloadType] as WorkloadType); + if (!workloadType) { + return WorkloadType.None; + } + return workloadType; +} diff --git a/src/Contracts/DataModels.ts b/src/Contracts/DataModels.ts index da6ddd28d..47533fee3 100644 --- a/src/Contracts/DataModels.ts +++ b/src/Contracts/DataModels.ts @@ -6,6 +6,7 @@ export interface ArmEntity { location: string; type: string; kind: string; + tags?: Tags; } export interface DatabaseAccount extends ArmEntity { @@ -663,3 +664,5 @@ export interface FeatureRegistration { state: string; }; } + +export type Tags = { [key: string]: string }; diff --git a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx index 2001700ad..64676bbc3 100644 --- a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx +++ b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx @@ -1,4 +1,5 @@ import { Checkbox, DirectionalHint, Link, Stack, Text, TextField, TooltipHost } from "@fluentui/react"; +import { getWorkloadType } from "Common/DatabaseAccountUtility"; import { useDatabases } from "Explorer/useDatabases"; import React, { FunctionComponent, useEffect, useState } from "react"; import * as Constants from "../../../Common/Constants"; @@ -34,10 +35,15 @@ export const ThroughputInput: FunctionComponent = ({ setIsThroughputCapExceeded, onCostAcknowledgeChange, }: ThroughputInputProps) => { + const defaultThroughput: number = + isFreeTier || + isQuickstart || + [Constants.WorkloadType.Learning, Constants.WorkloadType.DevelopmentTesting].includes(getWorkloadType()) + ? AutoPilotUtils.autoPilotThroughput1K + : AutoPilotUtils.autoPilotThroughput4K; + const [isAutoscaleSelected, setIsAutoScaleSelected] = useState(true); - const [throughput, setThroughput] = useState( - isFreeTier || isQuickstart ? AutoPilotUtils.autoPilotThroughput1K : AutoPilotUtils.autoPilotThroughput4K, - ); + const [throughput, setThroughput] = useState(defaultThroughput); const [isCostAcknowledged, setIsCostAcknowledged] = useState(false); const [throughputError, setThroughputError] = useState(""); const [totalThroughputUsed, setTotalThroughputUsed] = useState(0); @@ -47,7 +53,6 @@ export const ThroughputInput: FunctionComponent = ({ const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit; const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1; - useEffect(() => { // throughput cap check for the initial state let totalThroughput = 0; @@ -157,9 +162,6 @@ export const ThroughputInput: FunctionComponent = ({ const handleOnChangeMode = (event: React.ChangeEvent, mode: string): void => { if (mode === "Autoscale") { - const defaultThroughput = isFreeTier - ? AutoPilotUtils.autoPilotThroughput1K - : AutoPilotUtils.autoPilotThroughput4K; setThroughput(defaultThroughput); setIsAutoScaleSelected(true); setThroughputValue(defaultThroughput); From bd592d07afc36fa49d11b917e958a96f60d1c456 Mon Sep 17 00:00:00 2001 From: SATYA SB <107645008+satya07sb@users.noreply.github.com> Date: Wed, 12 Feb 2025 11:31:30 +0530 Subject: [PATCH 06/17] [accessibility-1217621]: Keyboard focus gets lost on the page which opens after activating "Data Explorer" menu item present under 'Overview' page. (#1927) Co-authored-by: Satyapriya Bai --- src/Explorer/Sidebar.tsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Explorer/Sidebar.tsx b/src/Explorer/Sidebar.tsx index a6a0e6a3e..8b96b0422 100644 --- a/src/Explorer/Sidebar.tsx +++ b/src/Explorer/Sidebar.tsx @@ -26,7 +26,7 @@ import { getCollectionName, getDatabaseName } from "Utils/APITypeUtils"; import { Allotment, AllotmentHandle } from "allotment"; import { useSidePanel } from "hooks/useSidePanel"; import { debounce } from "lodash"; -import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; const useSidebarStyles = makeStyles({ sidebar: { @@ -109,6 +109,7 @@ interface GlobalCommand { icon: JSX.Element; onClick: () => void; keyboardAction?: KeyboardAction; + ref?: React.RefObject; } const GlobalCommands: React.FC = ({ explorer }) => { @@ -118,6 +119,7 @@ const GlobalCommands: React.FC = ({ explorer }) => { // However, that messes with the Menu positioning, so we need to get a reference to the 'div' to pass to the Menu. // We can't use a ref though, because it would be set after the Menu is rendered, so we use a state value to force a re-render. const [globalCommandButton, setGlobalCommandButton] = useState(null); + const primaryFocusableRef = useRef(null); const actions = useMemo(() => { if ( @@ -177,6 +179,16 @@ const GlobalCommands: React.FC = ({ explorer }) => { ); }, [actions, setKeyboardActions]); + useLayoutEffect(() => { + if (primaryFocusableRef.current) { + const timer = setTimeout(() => { + primaryFocusableRef.current.focus(); + }, 0); + return () => clearTimeout(timer); + } + return undefined; + }, []); + if (!primaryAction) { return null; } @@ -184,7 +196,7 @@ const GlobalCommands: React.FC = ({ explorer }) => { return (
{actions.length === 1 ? ( - ) : ( @@ -194,7 +206,7 @@ const GlobalCommands: React.FC = ({ explorer }) => {
From 99378582ce141145a3d6d84b031a97c4a459582a Mon Sep 17 00:00:00 2001 From: sunghyunkang1111 <114709653+sunghyunkang1111@users.noreply.github.com> Date: Wed, 12 Feb 2025 13:09:52 -0600 Subject: [PATCH 07/17] Remove blocking await on sample database (#2047) * Remove blocking await on sample database * Remove compress flag to reduce bundle size * Fix typo in webpack config comment date --- src/Explorer/Explorer.tsx | 41 ++++++++++++++++---------------- src/hooks/useKnockoutExplorer.ts | 2 +- webpack.config.js | 3 ++- 3 files changed, 24 insertions(+), 22 deletions(-) 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/hooks/useKnockoutExplorer.ts b/src/hooks/useKnockoutExplorer.ts index 8fc07ae4b..f1320c3c7 100644 --- a/src/hooks/useKnockoutExplorer.ts +++ b/src/hooks/useKnockoutExplorer.ts @@ -820,7 +820,7 @@ async function updateContextForSampleData(explorer: Explorer): Promise { const sampleDataConnectionInfo = parseResourceTokenConnectionString(data.connectionString); updateUserContext({ sampleDataConnectionInfo }); - await explorer.refreshSampleData(); + explorer.refreshSampleData(); } interface SampledataconnectionResponse { diff --git a/webpack.config.js b/webpack.config.js index 859affc44..21858c2b8 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -248,7 +248,8 @@ module.exports = function (_env = {}, argv = {}) { new TerserPlugin({ terserOptions: { // These options increase our initial bundle size by ~5% but the builds are significantly faster and won't run out of memory - compress: false, + // Update 2/11/2025: we are removing this flag as our bundles sizes grew so that it can remove dead and unreachable code with compromise of build time + // compress: false, mangle: { keep_fnames: true, keep_classnames: true, From de2449ee252fc2db46fd41b6a94517b5e4b2985c Mon Sep 17 00:00:00 2001 From: sunghyunkang1111 <114709653+sunghyunkang1111@users.noreply.github.com> Date: Wed, 12 Feb 2025 13:10:07 -0600 Subject: [PATCH 08/17] Adding throughput bucket settings in Data Explorer (#2044) * Added throughput bucketing * fix bugs * enable/disable per autoscale selection * Added logic * change query bucket to group * Updated to a tab * Fixed unit tests * Edit package-lock * Compile build fix * fix unit tests * moving the throughput bucket flag to the client generation level --- src/Common/CosmosClient.ts | 2 + src/Common/dataAccess/readCollectionOffer.ts | 4 + src/Common/dataAccess/updateOffer.ts | 9 +- src/Contracts/DataModels.ts | 7 + .../Settings/SettingsComponent.test.tsx | 40 ++++ .../Controls/Settings/SettingsComponent.tsx | 68 ++++++- .../ThroughputBucketsComponent.test.tsx | 177 ++++++++++++++++++ .../ThroughputBucketsComponent.tsx | 105 +++++++++++ .../Controls/Settings/SettingsUtils.tsx | 6 +- .../Tabs/QueryTab/QueryTabComponent.tsx | 1 + src/Platform/Hosted/extractFeatures.ts | 2 + .../cosmos/cassandraResources.ts | 4 +- .../arm/generatedClients/cosmos/collection.ts | 4 +- .../cosmos/collectionPartition.ts | 4 +- .../cosmos/collectionPartitionRegion.ts | 4 +- .../cosmos/collectionRegion.ts | 4 +- .../arm/generatedClients/cosmos/database.ts | 4 +- .../cosmos/databaseAccountRegion.ts | 4 +- .../cosmos/databaseAccounts.ts | 4 +- .../generatedClients/cosmos/graphResources.ts | 4 +- .../cosmos/gremlinResources.ts | 4 +- .../arm/generatedClients/cosmos/locations.ts | 4 +- .../cosmos/mongoDBResources.ts | 4 +- .../arm/generatedClients/cosmos/operations.ts | 4 +- .../cosmos/partitionKeyRangeId.ts | 4 +- .../cosmos/partitionKeyRangeIdRegion.ts | 4 +- .../arm/generatedClients/cosmos/percentile.ts | 4 +- .../cosmos/percentileSourceTarget.ts | 4 +- .../cosmos/percentileTarget.ts | 4 +- .../generatedClients/cosmos/sqlResources.ts | 4 +- .../generatedClients/cosmos/tableResources.ts | 4 +- .../arm/generatedClients/cosmos/types.ts | 147 ++++++++++----- utils/armClientGenerator/generator.ts | 8 +- 33 files changed, 560 insertions(+), 96 deletions(-) create mode 100644 src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent.test.tsx create mode 100644 src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent.tsx diff --git a/src/Common/CosmosClient.ts b/src/Common/CosmosClient.ts index 2779e1ba1..da84e0c07 100644 --- a/src/Common/CosmosClient.ts +++ b/src/Common/CosmosClient.ts @@ -203,8 +203,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/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/Contracts/DataModels.ts b/src/Contracts/DataModels.ts index 47533fee3..3b3ab5027 100644 --- a/src/Contracts/DataModels.ts +++ b/src/Contracts/DataModels.ts @@ -275,6 +275,12 @@ export interface Offer { offerReplacePending: boolean; instantMaximumThroughput?: number; softAllowedMaximumThroughput?: number; + throughputBuckets?: ThroughputBucket[]; +} + +export interface ThroughputBucket { + id: number; + maxThroughputPercentage: number; } export interface SDKOfferDefinition extends Resource { @@ -397,6 +403,7 @@ export interface UpdateOfferParams { collectionId?: string; migrateToAutoPilot?: boolean; migrateToManual?: boolean; + throughputBuckets?: ThroughputBucket[]; } export interface Notification { 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/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/Platform/Hosted/extractFeatures.ts b/src/Platform/Hosted/extractFeatures.ts index 5bd84516e..175c4bcff 100644 --- a/src/Platform/Hosted/extractFeatures.ts +++ b/src/Platform/Hosted/extractFeatures.ts @@ -16,6 +16,7 @@ export type Features = { readonly enableAadDataPlane: boolean; readonly enableResourceGraph: boolean; readonly enableKoResourceTree: boolean; + readonly enableThroughputBuckets: boolean; readonly hostedDataExplorer: boolean; readonly junoEndpoint?: string; readonly phoenixEndpoint?: string; @@ -81,6 +82,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear enableSpark: "true" === get("enablespark"), enableTtl: "true" === get("enablettl"), enableKoResourceTree: "true" === get("enablekoresourcetree"), + enableThroughputBuckets: "true" === get("enablethroughputbuckets"), executeSproc: "true" === get("dataexplorerexecutesproc"), hostedDataExplorer: "true" === get("hosteddataexplorerenabled"), mongoProxyEndpoint: get("mongoproxyendpoint"), diff --git a/src/Utils/arm/generatedClients/cosmos/cassandraResources.ts b/src/Utils/arm/generatedClients/cosmos/cassandraResources.ts index 461e516bf..9c573b9ad 100644 --- a/src/Utils/arm/generatedClients/cosmos/cassandraResources.ts +++ b/src/Utils/arm/generatedClients/cosmos/cassandraResources.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json */ import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -const apiVersion = "2024-02-15-preview"; +const apiVersion = "2024-12-01-preview"; /* Lists the Cassandra keyspaces under an existing Azure Cosmos DB database account. */ export async function listCassandraKeyspaces( diff --git a/src/Utils/arm/generatedClients/cosmos/collection.ts b/src/Utils/arm/generatedClients/cosmos/collection.ts index 4a9a9c198..da781b9f4 100644 --- a/src/Utils/arm/generatedClients/cosmos/collection.ts +++ b/src/Utils/arm/generatedClients/cosmos/collection.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json */ import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -const apiVersion = "2024-02-15-preview"; +const apiVersion = "2024-12-01-preview"; /* Retrieves the metrics determined by the given filter for the given database account and collection. */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/collectionPartition.ts b/src/Utils/arm/generatedClients/cosmos/collectionPartition.ts index 6fed487b6..7996d9f15 100644 --- a/src/Utils/arm/generatedClients/cosmos/collectionPartition.ts +++ b/src/Utils/arm/generatedClients/cosmos/collectionPartition.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json */ import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -const apiVersion = "2024-02-15-preview"; +const apiVersion = "2024-12-01-preview"; /* Retrieves the metrics determined by the given filter for the given collection, split by partition. */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/collectionPartitionRegion.ts b/src/Utils/arm/generatedClients/cosmos/collectionPartitionRegion.ts index b33c904d9..5e6f65265 100644 --- a/src/Utils/arm/generatedClients/cosmos/collectionPartitionRegion.ts +++ b/src/Utils/arm/generatedClients/cosmos/collectionPartitionRegion.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json */ import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -const apiVersion = "2024-02-15-preview"; +const apiVersion = "2024-12-01-preview"; /* Retrieves the metrics determined by the given filter for the given collection and region, split by partition. */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/collectionRegion.ts b/src/Utils/arm/generatedClients/cosmos/collectionRegion.ts index 984cb146a..03da69029 100644 --- a/src/Utils/arm/generatedClients/cosmos/collectionRegion.ts +++ b/src/Utils/arm/generatedClients/cosmos/collectionRegion.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json */ import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -const apiVersion = "2024-02-15-preview"; +const apiVersion = "2024-12-01-preview"; /* Retrieves the metrics determined by the given filter for the given database account, collection and region. */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/database.ts b/src/Utils/arm/generatedClients/cosmos/database.ts index 7b286c4ec..4cf7df0ef 100644 --- a/src/Utils/arm/generatedClients/cosmos/database.ts +++ b/src/Utils/arm/generatedClients/cosmos/database.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json */ import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -const apiVersion = "2024-02-15-preview"; +const apiVersion = "2024-12-01-preview"; /* Retrieves the metrics determined by the given filter for the given database account and database. */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/databaseAccountRegion.ts b/src/Utils/arm/generatedClients/cosmos/databaseAccountRegion.ts index 09f17c35b..bc9a611b8 100644 --- a/src/Utils/arm/generatedClients/cosmos/databaseAccountRegion.ts +++ b/src/Utils/arm/generatedClients/cosmos/databaseAccountRegion.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json */ import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -const apiVersion = "2024-02-15-preview"; +const apiVersion = "2024-12-01-preview"; /* Retrieves the metrics determined by the given filter for the given database account and region. */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/databaseAccounts.ts b/src/Utils/arm/generatedClients/cosmos/databaseAccounts.ts index 4b52b631b..f4b35a0a2 100644 --- a/src/Utils/arm/generatedClients/cosmos/databaseAccounts.ts +++ b/src/Utils/arm/generatedClients/cosmos/databaseAccounts.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json */ import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -const apiVersion = "2024-02-15-preview"; +const apiVersion = "2024-12-01-preview"; /* Retrieves the properties of an existing Azure Cosmos DB database account. */ export async function get( diff --git a/src/Utils/arm/generatedClients/cosmos/graphResources.ts b/src/Utils/arm/generatedClients/cosmos/graphResources.ts index d51d44d77..b304ff3e1 100644 --- a/src/Utils/arm/generatedClients/cosmos/graphResources.ts +++ b/src/Utils/arm/generatedClients/cosmos/graphResources.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json */ import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -const apiVersion = "2024-02-15-preview"; +const apiVersion = "2024-12-01-preview"; /* Lists the graphs under an existing Azure Cosmos DB database account. */ export async function listGraphs( diff --git a/src/Utils/arm/generatedClients/cosmos/gremlinResources.ts b/src/Utils/arm/generatedClients/cosmos/gremlinResources.ts index 6e8656400..821faa6ad 100644 --- a/src/Utils/arm/generatedClients/cosmos/gremlinResources.ts +++ b/src/Utils/arm/generatedClients/cosmos/gremlinResources.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json */ import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -const apiVersion = "2024-02-15-preview"; +const apiVersion = "2024-12-01-preview"; /* Lists the Gremlin databases under an existing Azure Cosmos DB database account. */ export async function listGremlinDatabases( diff --git a/src/Utils/arm/generatedClients/cosmos/locations.ts b/src/Utils/arm/generatedClients/cosmos/locations.ts index 6ec4cdc62..00cc27092 100644 --- a/src/Utils/arm/generatedClients/cosmos/locations.ts +++ b/src/Utils/arm/generatedClients/cosmos/locations.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json */ import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -const apiVersion = "2024-02-15-preview"; +const apiVersion = "2024-12-01-preview"; /* List Cosmos DB locations and their properties */ export async function list(subscriptionId: string): Promise { diff --git a/src/Utils/arm/generatedClients/cosmos/mongoDBResources.ts b/src/Utils/arm/generatedClients/cosmos/mongoDBResources.ts index 22b316904..24fbae936 100644 --- a/src/Utils/arm/generatedClients/cosmos/mongoDBResources.ts +++ b/src/Utils/arm/generatedClients/cosmos/mongoDBResources.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json */ import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -const apiVersion = "2024-02-15-preview"; +const apiVersion = "2024-12-01-preview"; /* Lists the MongoDB databases under an existing Azure Cosmos DB database account. */ export async function listMongoDBDatabases( diff --git a/src/Utils/arm/generatedClients/cosmos/operations.ts b/src/Utils/arm/generatedClients/cosmos/operations.ts index ae87fbb48..e2dd9589f 100644 --- a/src/Utils/arm/generatedClients/cosmos/operations.ts +++ b/src/Utils/arm/generatedClients/cosmos/operations.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json */ import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -const apiVersion = "2024-02-15-preview"; +const apiVersion = "2024-12-01-preview"; /* Lists all of the available Cosmos DB Resource Provider operations. */ export async function list(): Promise { diff --git a/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeId.ts b/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeId.ts index d9b5d6dfa..c977f0905 100644 --- a/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeId.ts +++ b/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeId.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json */ import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -const apiVersion = "2024-02-15-preview"; +const apiVersion = "2024-12-01-preview"; /* Retrieves the metrics determined by the given filter for the given partition key range id. */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeIdRegion.ts b/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeIdRegion.ts index 6ec2ba50a..557dfde06 100644 --- a/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeIdRegion.ts +++ b/src/Utils/arm/generatedClients/cosmos/partitionKeyRangeIdRegion.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json */ import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -const apiVersion = "2024-02-15-preview"; +const apiVersion = "2024-12-01-preview"; /* Retrieves the metrics determined by the given filter for the given partition key range id and region. */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/percentile.ts b/src/Utils/arm/generatedClients/cosmos/percentile.ts index cbbc751dc..03f0b3ea3 100644 --- a/src/Utils/arm/generatedClients/cosmos/percentile.ts +++ b/src/Utils/arm/generatedClients/cosmos/percentile.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json */ import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -const apiVersion = "2024-02-15-preview"; +const apiVersion = "2024-12-01-preview"; /* Retrieves the metrics determined by the given filter for the given database account. This url is only for PBS and Replication Latency data */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/percentileSourceTarget.ts b/src/Utils/arm/generatedClients/cosmos/percentileSourceTarget.ts index 88c05dd11..fc1e39013 100644 --- a/src/Utils/arm/generatedClients/cosmos/percentileSourceTarget.ts +++ b/src/Utils/arm/generatedClients/cosmos/percentileSourceTarget.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json */ import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -const apiVersion = "2024-02-15-preview"; +const apiVersion = "2024-12-01-preview"; /* Retrieves the metrics determined by the given filter for the given account, source and target region. This url is only for PBS and Replication Latency data */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/percentileTarget.ts b/src/Utils/arm/generatedClients/cosmos/percentileTarget.ts index 87359e9f0..eaf1f3947 100644 --- a/src/Utils/arm/generatedClients/cosmos/percentileTarget.ts +++ b/src/Utils/arm/generatedClients/cosmos/percentileTarget.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json */ import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -const apiVersion = "2024-02-15-preview"; +const apiVersion = "2024-12-01-preview"; /* Retrieves the metrics determined by the given filter for the given account target region. This url is only for PBS and Replication Latency data */ export async function listMetrics( diff --git a/src/Utils/arm/generatedClients/cosmos/sqlResources.ts b/src/Utils/arm/generatedClients/cosmos/sqlResources.ts index 049e265e9..bcf7c6bac 100644 --- a/src/Utils/arm/generatedClients/cosmos/sqlResources.ts +++ b/src/Utils/arm/generatedClients/cosmos/sqlResources.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json */ import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -const apiVersion = "2024-05-15-preview"; +const apiVersion = "2024-12-01-preview"; /* Lists the SQL databases under an existing Azure Cosmos DB database account. */ export async function listSqlDatabases( diff --git a/src/Utils/arm/generatedClients/cosmos/tableResources.ts b/src/Utils/arm/generatedClients/cosmos/tableResources.ts index 0da78793e..e0771c5d3 100644 --- a/src/Utils/arm/generatedClients/cosmos/tableResources.ts +++ b/src/Utils/arm/generatedClients/cosmos/tableResources.ts @@ -3,13 +3,13 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json */ import { configContext } from "../../../../ConfigContext"; import { armRequest } from "../../request"; import * as Types from "./types"; -const apiVersion = "2024-02-15-preview"; +const apiVersion = "2024-12-01-preview"; /* Lists the Tables under an existing Azure Cosmos DB database account. */ export async function listTables( diff --git a/src/Utils/arm/generatedClients/cosmos/types.ts b/src/Utils/arm/generatedClients/cosmos/types.ts index 1871884e0..6f64ebd7a 100644 --- a/src/Utils/arm/generatedClients/cosmos/types.ts +++ b/src/Utils/arm/generatedClients/cosmos/types.ts @@ -3,7 +3,7 @@ Run "npm run generateARMClients" to regenerate Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs - Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json + Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json */ /* The List operation response, that contains the client encryption keys and their properties. */ @@ -553,6 +553,12 @@ export interface DatabaseAccountGetProperties { /* The object that represents all properties related to capacity enforcement on an account. */ capacity?: Capacity; + /* Indicates the capacityMode of the Cosmos DB account. */ + capacityMode?: CapacityMode; + + /* The object that represents the migration state for the CapacityMode of the Cosmos DB account. */ + capacityModeChangeTransitionState?: CapacityModeChangeTransitionState; + /* Flag to indicate whether to enable MaterializedViews on the Cosmos DB account */ enableMaterializedViews?: boolean; /* The object that represents the metadata for the Account Keys of the Cosmos DB account. */ @@ -652,6 +658,9 @@ export interface DatabaseAccountCreateUpdateProperties { /* The object that represents all properties related to capacity enforcement on an account. */ capacity?: Capacity; + /* Indicates the capacityMode of the Cosmos DB account. */ + capacityMode?: CapacityMode; + /* Flag to indicate whether to enable MaterializedViews on the Cosmos DB account */ enableMaterializedViews?: boolean; /* This property is ignored during the update/create operation, as the metadata is read-only. The object represents the metadata for the Account Keys of the Cosmos DB account. */ @@ -754,6 +763,9 @@ export interface DatabaseAccountUpdateProperties { /* The object that represents all properties related to capacity enforcement on an account. */ capacity?: Capacity; + /* Indicates the capacityMode of the Cosmos DB account. */ + capacityMode?: CapacityMode; + /* Flag to indicate whether to enable MaterializedViews on the Cosmos DB account */ enableMaterializedViews?: boolean; /* This property is ignored during the update operation, as the metadata is read-only. The object represents the metadata for the Account Keys of the Cosmos DB account. */ @@ -1087,6 +1099,8 @@ export interface ThroughputSettingsResource { readonly instantMaximumThroughput?: string; /* The maximum throughput value or the maximum maxThroughput value (for autoscale) that can be specified */ readonly softAllowedMaximumThroughput?: string; + /* Array of Throughput Bucket limits to be applied to the Cosmos DB container */ + throughputBuckets?: ThroughputBucketResource[]; } /* Cosmos DB provisioned throughput settings object */ @@ -1114,6 +1128,14 @@ export interface ThroughputPolicyResource { incrementPercent?: number; } +/* Cosmos DB throughput bucket object */ +export interface ThroughputBucketResource { + /* Represents the throughput bucket id */ + id: number; + /* Represents maximum percentage throughput that can be used by the bucket */ + maxThroughputPercentage: number; +} + /* Cosmos DB options resource object */ export interface OptionsResource { /* Value of the Cosmos DB resource throughput or autoscaleSettings. Use the ThroughputSetting resource when retrieving offer details. */ @@ -1235,10 +1257,6 @@ export interface SqlDatabaseResource { export interface SqlContainerResource { /* Name of the Cosmos DB SQL container */ id: string; - - vectorEmbeddingPolicy?: VectorEmbeddingPolicy; - fullTextPolicy?: FullTextPolicy; - /* The configuration of the indexing policy. By default, the indexing is automatic for all document paths within the container */ indexingPolicy?: IndexingPolicy; @@ -1269,39 +1287,11 @@ export interface SqlContainerResource { /* List of computed properties */ computedProperties?: ComputedProperty[]; -} -export interface VectorEmbeddingPolicy { - vectorEmbeddings: VectorEmbedding[]; -} + /* The vector embedding policy for the container. */ + vectorEmbeddingPolicy?: VectorEmbeddingPolicy; -export interface VectorEmbedding { - path?: string; - dataType?: string; - dimensions?: number; - distanceFunction?: string; -} - -export interface FullTextPolicy { - /** - * The default language for the full text . - */ - defaultLanguage: string; - /** - * The paths to be indexed for full text search. - */ - fullTextPaths: FullTextPath[]; -} - -export interface FullTextPath { - /** - * The path to be indexed for full text search. - */ - path: string; - /** - * The language for the full text path. - */ - language: string; + fullTextPolicy?: FullTextPolicy; } /* Cosmos DB indexing policy */ @@ -1323,19 +1313,14 @@ export interface IndexingPolicy { /* List of spatial specifics */ spatialIndexes?: SpatialSpec[]; + /* List of paths to include in the vector indexing */ vectorIndexes?: VectorIndex[]; - - fullTextIndexes?: FullTextIndex[]; } -export interface VectorIndex { - path?: string; - type?: string; -} - -export interface FullTextIndex { - /** The path in the JSON document to index. */ - path: string; +/* Cosmos DB Vector Embedding Policy */ +export interface VectorEmbeddingPolicy { + /* List of vector embeddings */ + vectorEmbeddings?: VectorEmbedding[]; } /* undocumented */ @@ -1363,6 +1348,50 @@ export interface Indexes { kind?: "Hash" | "Range" | "Spatial"; } +/* undocumented */ +export interface VectorIndex { + /* The path to the vector field in the document. */ + path: string; + /* The index type of the vector. Currently, flat, diskANN, and quantizedFlat are supported. */ + type: "flat" | "diskANN" | "quantizedFlat"; +} + +/* Represents a vector embedding. A vector embedding is used to define a vector field in the documents. */ +export interface VectorEmbedding { + /* The path to the vector field in the document. */ + path: string; + /* Indicates the data type of vector. */ + dataType: "float16" | "float32" | "uint8" | "int8"; + + /* The distance function to use for distance calculation in between vectors. */ + distanceFunction: "euclidean" | "cosine" | "dotproduct"; + + /* The number of dimensions in the vector. */ + dimensions: number; +} + +export interface FullTextPolicy { + /** + * The default language for the full text . + */ + defaultLanguage: string; + /** + * The paths to be indexed for full text search. + */ + fullTextPaths: FullTextPath[]; +} + +export interface FullTextPath { + /** + * The path to be indexed for full text search. + */ + path: string; + /** + * The language for the full text path. + */ + language: string; +} + /* List of composite path */ export type CompositePathList = CompositePath[]; @@ -1688,6 +1717,28 @@ export interface Capacity { totalThroughputLimit?: number; } +/* Indicates the capacity mode of the account. */ +export type CapacityMode = "None" | "Provisioned" | "Serverless"; + +/* The transition state information related capacity mode change with update request. */ +export interface CapacityModeChangeTransitionState { + /* The transition status of capacity mode. */ + capacityModeTransitionStatus?: "Invalid" | "Initialized" | "InProgress" | "Completed" | "Failed"; + + /* Indicates the current capacity mode of the account. */ + currentCapacityMode?: "None" | "Provisioned" | "Serverless"; + + /* Indicates the previous capacity mode of the account before successful transition. */ + previousCapacityMode?: "None" | "Provisioned" | "Serverless"; + + /* Begin time in UTC of the capacity mode change. */ + readonly capacityModeTransitionBeginTimestamp?: string; + /* End time in UTC of the capacity mode change. */ + readonly capacityModeTransitionEndTimestamp?: string; + /* End time in UTC of the last successful capacity mode change. */ + readonly capacityModeLastSuccessfulTransitionEndTimestamp?: string; +} + /* Tags are a list of key-value pairs that describe the resource. These tags can be used in viewing and grouping this resource (across resource groups). A maximum of 15 tags can be provided for a resource. Each tag must have a key no greater than 128 characters and value no greater than 256 characters. For example, the default experience for a template type is set with "defaultExperience": "Cassandra". Current "defaultExperience" values also include "Table", "Graph", "DocumentDB", and "MongoDB". */ export type Tags = { [key: string]: string }; @@ -1953,8 +2004,8 @@ export type PublicNetworkAccess = "Enabled" | "Disabled" | "SecuredByPerimeter"; /* undocumented */ export interface ApiProperties { - /* Describes the ServerVersion of an a MongoDB account. */ - serverVersion?: "3.2" | "3.6" | "4.0" | "4.2"; + /* Describes the version of the MongoDB account. */ + serverVersion?: "3.2" | "3.6" | "4.0" | "4.2" | "5.0" | "6.0" | "7.0"; } /* Analytical storage specific properties. */ diff --git a/utils/armClientGenerator/generator.ts b/utils/armClientGenerator/generator.ts index 1a50e9082..69c86c41a 100644 --- a/utils/armClientGenerator/generator.ts +++ b/utils/armClientGenerator/generator.ts @@ -16,13 +16,13 @@ Results of this file should be checked into the repo. */ // CHANGE THESE VALUES TO GENERATE NEW CLIENTS -const version = "2024-02-15-preview"; -/* The following are legal options for resourceName but you generally will only use cosmos-db: -"cosmos-db" | "managedCassandra" | "mongorbac" | "notebook" | "privateEndpointConnection" | "privateLinkResources" | +const version = "2024-12-01-preview"; +/* The following are legal options for resourceName but you generally will only use cosmos: +"cosmos" | "managedCassandra" | "mongorbac" | "notebook" | "privateEndpointConnection" | "privateLinkResources" | "rbac" | "restorable" | "services" | "dataTransferService" */ const githubResourceName = "cosmos-db"; -const deResourceName = "cosmos-db"; +const deResourceName = "cosmos"; const schemaURL = `https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/${version}/${githubResourceName}.json`; const outputDir = path.join(__dirname, `../../src/Utils/arm/generatedClients/${deResourceName}`); From 2730da7ab62a3cd94bd19837741e58ddb73fa282 Mon Sep 17 00:00:00 2001 From: bogercraig <124094535+bogercraig@users.noreply.github.com> Date: Wed, 12 Feb 2025 18:12:59 -0800 Subject: [PATCH 09/17] Backend Migration - Remove Use of Legacy Backend from DE (#2043) * Default to new backend endpoint if the endpoint in current context does not match existing set in constants. * Remove some env references. * Added comments with reasoning for selecting new backend by default. * Update comment. * Remove all references to useNewPortalBackendEndpoint now that old backend is disabled in all environments. * Resolve lint issues. * Removed references to old backend from Cassandra and Mongo Apis * fix unit tests --------- Co-authored-by: Asier Isayas --- src/Common/CosmosClient.ts | 35 +- src/Common/MongoProxyClient.test.ts | 61 +-- src/Common/MongoProxyClient.ts | 459 +----------------- src/ConfigContext.ts | 17 - src/Contracts/ViewModels.ts | 1 - src/Explorer/Notebook/useNotebook.ts | 5 +- .../QueryCopilot/Shared/QueryCopilotClient.ts | 6 +- src/Explorer/Tables/TableDataClient.ts | 199 +------- .../Tabs/DocumentsTabV2/DocumentsTabV2.tsx | 36 +- .../MongoShellTab/MongoShellTabComponent.tsx | 18 +- .../MongoShellTab/getMongoShellUrl.test.ts | 12 +- .../Tabs/MongoShellTab/getMongoShellUrl.ts | 4 +- src/Explorer/Tabs/Tabs.tsx | 84 ---- .../Hosted/Components/ConnectExplorer.tsx | 28 +- src/Utils/EndpointUtils.ts | 70 +-- src/hooks/useKnockoutExplorer.ts | 26 +- src/hooks/usePortalAccessToken.tsx | 28 +- test/testExplorer/TestExplorer.ts | 1 - 18 files changed, 42 insertions(+), 1048 deletions(-) diff --git a/src/Common/CosmosClient.ts b/src/Common/CosmosClient.ts index da84e0c07..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, 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/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/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/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/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.`} - - )}
diff --git a/src/Explorer/Menus/NotificationConsole/__snapshots__/NotificationConsoleComponent.test.tsx.snap b/src/Explorer/Menus/NotificationConsole/__snapshots__/NotificationConsoleComponent.test.tsx.snap index 57267f1e0..268b110b0 100644 --- a/src/Explorer/Menus/NotificationConsole/__snapshots__/NotificationConsoleComponent.test.tsx.snap +++ b/src/Explorer/Menus/NotificationConsole/__snapshots__/NotificationConsoleComponent.test.tsx.snap @@ -21,7 +21,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = ` className="notificationConsoleHeaderIconWithData" > in progress items error items info items
ChevronUpIcon
@@ -192,7 +192,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = ` className="notificationConsoleHeaderIconWithData" > in progress items error items info items
ChevronUpIcon
From 3fcbdf61529bf247264cd76e86fadbb46b37a147 Mon Sep 17 00:00:00 2001 From: SATYA SB <107645008+satya07sb@users.noreply.github.com> Date: Wed, 19 Feb 2025 11:26:15 +0530 Subject: [PATCH 14/17] [accessibility-3739790-3739677]:[Forms and Validation - Azure Cosmos DB- Data Explorer - New Vertex]: Visual Label is not defined for Key, Value and Type input fields under 'New Vertex' pane. (#2040) Co-authored-by: Satyapriya Bai --- .../Graph/NewVertexComponent/NewVertexComponent.less | 8 ++++---- .../Graph/NewVertexComponent/NewVertexComponent.tsx | 9 ++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.less b/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.less index 7f214d22d..6993cbe39 100644 --- a/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.less +++ b/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.less @@ -14,10 +14,6 @@ .flex-direction(@direction: row); padding: 4px 5px; - label { - padding: 0px; - } - .valueCol { flex-grow: 1; padding-right: 5px; @@ -63,6 +59,10 @@ height: 100%; } + .customTrashIcon { + padding-top: 33px; + } + .rightPaneTrashIconImg { vertical-align: top; } diff --git a/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.tsx b/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.tsx index 6b20cfcb0..8701bfb28 100644 --- a/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.tsx +++ b/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.tsx @@ -142,10 +142,11 @@ export const NewVertexComponent: FunctionComponent = (
= ( onChange={(event: React.ChangeEvent) => onKeyChange(event, index)} />
-
= (
= (
Date: Thu, 20 Feb 2025 07:06:25 -0800 Subject: [PATCH 15/17] Change value of the example SelfServeType enum to match name of (#2062) localization file. --- src/SelfServe/SelfServeUtils.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SelfServe/SelfServeUtils.tsx b/src/SelfServe/SelfServeUtils.tsx index f5b144915..ef4cb614c 100644 --- a/src/SelfServe/SelfServeUtils.tsx +++ b/src/SelfServe/SelfServeUtils.tsx @@ -30,7 +30,7 @@ export enum SelfServeType { invalid = "invalid", // Add your self serve types here // NOTE: text and casing of the enum's value must match the corresponding file in Localization\en\ - example = "example", + example = "SelfServeExample", sqlx = "SqlX", graphapicompute = "GraphAPICompute", materializedviewsbuilder = "MaterializedViewsBuilder", From a04eaff6be10893541cb16211805c8f003a6ff5c Mon Sep 17 00:00:00 2001 From: jawelton74 <103591340+jawelton74@users.noreply.github.com> Date: Thu, 20 Feb 2025 08:15:53 -0800 Subject: [PATCH 16/17] Add Tables to missing api type checks for dataplane RBAC. (#2060) * Add Tables to missing api type checks for dataplane RBAC. * Comment out test that is broken due to invalid hook call error. --- .../CommandBarComponentButtonFactory.test.ts | 34 +++++++++++-------- .../CommandBarComponentButtonFactory.tsx | 3 +- src/hooks/useKnockoutExplorer.ts | 2 +- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts index 0a4a805d2..8ac604ea2 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts @@ -37,21 +37,25 @@ describe("CommandBarComponentButtonFactory tests", () => { expect(enableAzureSynapseLinkBtn).toBeDefined(); }); - it("Button should not be visible for Tables API", () => { - updateUserContext({ - databaseAccount: { - properties: { - capabilities: [{ name: "EnableTable" }], - }, - } as DatabaseAccount, - }); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const enableAzureSynapseLinkBtn = buttons.find( - (button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel, - ); - expect(enableAzureSynapseLinkBtn).toBeUndefined(); - }); + // TODO: Now that Tables API supports dataplane RBAC, calling createStaticCommandBarButtons will enable the + // Entra ID Login button, which causes this test to fail due to "Invalid hook call.". This seems to be + // unsupported in jest and needs to be tested with react-hooks-testing-library. + // + // it("Button should not be visible for Tables API", () => { + // updateUserContext({ + // databaseAccount: { + // properties: { + // capabilities: [{ name: "EnableTable" }], + // }, + // } as DatabaseAccount, + // }); + // + // const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); + // const enableAzureSynapseLinkBtn = buttons.find( + // (button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel, + // ); + // expect(enableAzureSynapseLinkBtn).toBeUndefined(); + //}); it("Button should not be visible for Cassandra API", () => { updateUserContext({ diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx index 8c374a4c1..49c513a0a 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -1,4 +1,5 @@ import { KeyboardAction } from "KeyboardShortcuts"; +import { isDataplaneRbacSupported } from "Utils/APITypeUtils"; import * as React from "react"; import { useEffect, useState } from "react"; import AddSqlQueryIcon from "../../../../images/AddSqlQuery_16x16.svg"; @@ -61,7 +62,7 @@ export function createStaticCommandBarButtons( } } - if (userContext.apiType === "SQL") { + if (isDataplaneRbacSupported(userContext.apiType)) { const [loginButtonProps, setLoginButtonProps] = useState(undefined); const dataPlaneRbacEnabled = useDataPlaneRbac((state) => state.dataPlaneRbacEnabled); const aadTokenUpdated = useDataPlaneRbac((state) => state.aadTokenUpdated); diff --git a/src/hooks/useKnockoutExplorer.ts b/src/hooks/useKnockoutExplorer.ts index 70a772559..3c59baadf 100644 --- a/src/hooks/useKnockoutExplorer.ts +++ b/src/hooks/useKnockoutExplorer.ts @@ -553,7 +553,7 @@ async function configurePortal(): Promise { const { databaseAccount: account, subscriptionId, resourceGroup } = userContext; let dataPlaneRbacEnabled; - if (userContext.apiType === "SQL") { + if (isDataplaneRbacSupported(userContext.apiType)) { if (LocalStorageUtility.hasItem(StorageKey.DataPlaneRbacEnabled)) { const isDataPlaneRbacSetting = LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled); Logger.logInfo( From 14c9874e5ea92983f8d58503529999bdf9db151c Mon Sep 17 00:00:00 2001 From: SATYA SB <107645008+satya07sb@users.noreply.github.com> Date: Tue, 25 Feb 2025 13:35:59 +0530 Subject: [PATCH 17/17] [accessibility-3560325]:[Programmatic access - Cosmos DB Query Copilot - Query Faster with Copilot>Enable Query Advisor]: Element's role present under 'Sample Query1' tab does not support its ARIA attributes. (#2059) Co-authored-by: Satyapriya Bai --- src/Explorer/QueryCopilot/QueryCopilotPromptbar.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Explorer/QueryCopilot/QueryCopilotPromptbar.tsx b/src/Explorer/QueryCopilot/QueryCopilotPromptbar.tsx index e4a7b35de..1582a4335 100644 --- a/src/Explorer/QueryCopilot/QueryCopilotPromptbar.tsx +++ b/src/Explorer/QueryCopilot/QueryCopilotPromptbar.tsx @@ -393,8 +393,7 @@ export const QueryCopilotPromptbar: React.FC = ({ }, }} disabled={isGeneratingQuery} - autoComplete="list" - aria-expanded={showSamplePrompts} + autoComplete="off" placeholder="Ask a question in natural language and we’ll generate the query for you." aria-labelledby="copilot-textfield-label" onRenderSuffix={() => {