From 82f50f6b1f8253aa3f0dfa464ee5705cc66e0299 Mon Sep 17 00:00:00 2001 From: Sung-Hyun Kang Date: Thu, 14 Aug 2025 20:46:45 -0500 Subject: [PATCH] Added VNext feature matrix --- src/ConfigContext.ts | 2 + .../Controls/Settings/SettingsComponent.tsx | 11 +- src/Explorer/Explorer.tsx | 11 +- .../CommandBarComponentButtonFactory.tsx | 19 ++- .../AddCollectionPanel/AddCollectionPanel.tsx | 9 +- .../AddCollectionPanelUtility.tsx | 7 +- src/Utils/PlatformFeatureUtils.ts | 112 ++++++++++++++++++ src/hooks/useKnockoutExplorer.ts | 2 +- utils/local-proxy/index.js | 1 + 9 files changed, 160 insertions(+), 14 deletions(-) create mode 100644 src/Utils/PlatformFeatureUtils.ts diff --git a/src/ConfigContext.ts b/src/ConfigContext.ts index da35f7839..718cf0641 100644 --- a/src/ConfigContext.ts +++ b/src/ConfigContext.ts @@ -19,6 +19,7 @@ export enum Platform { Hosted = "Hosted", Emulator = "Emulator", Fabric = "Fabric", + VNextEmulator = "VNextEmulator", } export interface ConfigContext { @@ -225,6 +226,7 @@ export async function initializeConfiguration(): Promise { case Platform.Fabric: case Platform.Hosted: case Platform.Emulator: + case Platform.VNextEmulator: updateConfigContext({ platform }); } } diff --git a/src/Explorer/Controls/Settings/SettingsComponent.tsx b/src/Explorer/Controls/Settings/SettingsComponent.tsx index 6d8c875f8..27ba6254d 100644 --- a/src/Explorer/Controls/Settings/SettingsComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsComponent.tsx @@ -15,6 +15,7 @@ import { useDatabases } from "Explorer/useDatabases"; import { isFabricNative } from "Platform/Fabric/FabricUtil"; import { isVectorSearchEnabled } from "Utils/CapabilityUtils"; import { isRunningOnPublicCloud } from "Utils/CloudUtils"; +import { isFeatureSupported, PlatformFeature } from "Utils/PlatformFeatureUtils"; import * as React from "react"; import DiscardIcon from "../../../../images/discard.svg"; import SaveIcon from "../../../../images/save-cosmos.svg"; @@ -60,15 +61,15 @@ import { AddMongoIndexProps, ChangeFeedPolicyState, GeospatialConfigType, - MongoIndexTypes, - SettingsV2TabTypes, - TtlType, getMongoNotification, getTabTitle, hasDatabaseSharedThroughput, isDirty, + MongoIndexTypes, parseConflictResolutionMode, parseConflictResolutionProcedure, + SettingsV2TabTypes, + TtlType, } from "./SettingsUtils"; interface SettingsV2TabInfo { @@ -276,14 +277,14 @@ export class SettingsComponent extends React.Component { - return true; + return isFeatureSupported(PlatformFeature.UpdateCollection); }, }; this.discardSettingsChangesButton = { isEnabled: this.isDiscardSettingsButtonEnabled, isVisible: () => { - return true; + return isFeatureSupported(PlatformFeature.UpdateCollection); }, }; diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 91ad1b20b..76d25caa0 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -3,7 +3,7 @@ import { Link } from "@fluentui/react/lib/Link"; import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility"; import { Environment, getEnvironment } from "Common/EnvironmentUtility"; import { sendMessage } from "Common/MessageHandler"; -import { Platform, configContext } from "ConfigContext"; +import { configContext, Platform } from "ConfigContext"; import { MessageTypes } from "Contracts/ExplorerContracts"; import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane"; import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient"; @@ -18,6 +18,7 @@ import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils"; import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils"; import { featureRegistered } from "Utils/FeatureRegistrationUtils"; +import { isFeatureSupported, PlatformFeature } from "Utils/PlatformFeatureUtils"; import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts"; import { useQueryCopilot } from "hooks/useQueryCopilot"; import * as ko from "knockout"; @@ -1187,6 +1188,7 @@ export default class Explorer { // TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount const isNotebookEnabled = + isFeatureSupported(PlatformFeature.Notebooks) && configContext.platform !== Platform.Fabric && (userContext.features.notebooksDownBanner || useNotebook.getState().isPhoenixNotebooks || @@ -1194,7 +1196,11 @@ export default class Explorer { useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled); useNotebook .getState() - .setIsShellEnabled(useNotebook.getState().isPhoenixFeatures && isPublicInternetAccessAllowed()); + .setIsShellEnabled( + isFeatureSupported(PlatformFeature.CloudShell) && + useNotebook.getState().isPhoenixFeatures && + isPublicInternetAccessAllowed(), + ); TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, { isNotebookEnabled, @@ -1215,6 +1221,7 @@ export default class Explorer { public async configureCopilot(): Promise { if ( + !isFeatureSupported(PlatformFeature.Copilot) || userContext.apiType !== "SQL" || !userContext.subscriptionId || ![Environment.Development, Environment.Mpac, Environment.Prod].includes(getEnvironment()) diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx index fbf1b4434..9c2b1d779 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -1,5 +1,6 @@ import { KeyboardAction } from "KeyboardShortcuts"; import { isDataplaneRbacSupported } from "Utils/APITypeUtils"; +import { areAdvancedScriptsSupported, isFeatureSupported, PlatformFeature } from "Utils/PlatformFeatureUtils"; import * as React from "react"; import { useEffect, useState } from "react"; import AddSqlQueryIcon from "../../../../images/AddSqlQuery_16x16.svg"; @@ -17,7 +18,7 @@ import SynapseIcon from "../../../../images/synapse-link.svg"; import VSCodeIcon from "../../../../images/vscode.svg"; import { AuthType } from "../../../AuthType"; import * as Constants from "../../../Common/Constants"; -import { Platform, configContext } from "../../../ConfigContext"; +import { configContext, Platform } from "../../../ConfigContext"; import * as ViewModels from "../../../Contracts/ViewModels"; import { userContext } from "../../../UserContext"; import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils"; @@ -63,7 +64,9 @@ export function createStaticCommandBarButtons( } if (userContext.apiType !== "Gremlin") { const addVsCode = createOpenVsCodeDialogButton(container); - buttons.push(addVsCode); + if (addVsCode) { + buttons.push(addVsCode); + } } } @@ -242,11 +245,17 @@ export function createDivider(): CommandButtonComponentProps { function areScriptsSupported(): boolean { return ( - configContext.platform !== Platform.Fabric && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") + areAdvancedScriptsSupported() && + configContext.platform !== Platform.Fabric && + (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") ); } function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonComponentProps { + if (!isFeatureSupported(PlatformFeature.SynapseLink)) { + return undefined; + } + if (configContext.platform === Platform.Emulator) { return undefined; } @@ -274,6 +283,10 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo } function createOpenVsCodeDialogButton(container: Explorer): CommandButtonComponentProps { + if (!isFeatureSupported(PlatformFeature.VSCodeIntegration)) { + return undefined; + } + const label = "Visual Studio Code"; return { iconSrc: VSCodeIcon, diff --git a/src/Explorer/Panes/AddCollectionPanel/AddCollectionPanel.tsx b/src/Explorer/Panes/AddCollectionPanel/AddCollectionPanel.tsx index 323ed1bca..59a8bc9ff 100644 --- a/src/Explorer/Panes/AddCollectionPanel/AddCollectionPanel.tsx +++ b/src/Explorer/Panes/AddCollectionPanel/AddCollectionPanel.tsx @@ -50,6 +50,7 @@ import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor"; import { userContext } from "UserContext"; import { getCollectionName } from "Utils/APITypeUtils"; import { isCapabilityEnabled, isServerlessAccount, isVectorSearchEnabled } from "Utils/CapabilityUtils"; +import { isFeatureSupported, PlatformFeature } from "Utils/PlatformFeatureUtils"; import { getUpsellMessage } from "Utils/PricingUtils"; import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils"; import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils"; @@ -1160,11 +1161,15 @@ export class AddCollectionPanel extends React.Component> = new Map([ + [ + Platform.VNextEmulator, + new Set([ + PlatformFeature.Queries, + + PlatformFeature.CreateDatabase, + PlatformFeature.ReadDatabase, + PlatformFeature.DeleteDatabase, + + PlatformFeature.CreateCollection, + PlatformFeature.ReadCollection, + PlatformFeature.UpdateCollection, + PlatformFeature.DeleteCollection, + + PlatformFeature.CreateDocument, + PlatformFeature.ReadDocument, + PlatformFeature.UpdateDocument, + PlatformFeature.DeleteDocument, + ]), + ], +]); + +/** + * Central feature flag function - checks if a feature is enabled for current platform + * @param feature The feature to check + * @param platform Optional platform override, defaults to current platform + * @returns True if the feature is enabled for the platform, false otherwise + */ +export const isFeatureSupported = (feature: PlatformFeature, platform?: Platform): boolean => { + const currentPlatform = platform ?? configContext.platform; + if (currentPlatform !== Platform.VNextEmulator) { + return true; + } + // VNextEmulator: check from the feature matrix + const vnextFeatures = FEATURE_MATRIX.get(Platform.VNextEmulator); + return vnextFeatures?.has(feature) ?? false; +}; + +export const areAdvancedScriptsSupported = (platform?: Platform): boolean => { + const currentPlatform = platform ?? configContext.platform; + if (currentPlatform !== Platform.VNextEmulator) { + return true; + } + + // Otherwise, require all script features to be enabled + return ( + isFeatureSupported(PlatformFeature.StoredProcedures, currentPlatform) && + isFeatureSupported(PlatformFeature.UDF, currentPlatform) && + isFeatureSupported(PlatformFeature.Trigger, currentPlatform) + ); +}; diff --git a/src/hooks/useKnockoutExplorer.ts b/src/hooks/useKnockoutExplorer.ts index 79d9c635f..8dc7f303c 100644 --- a/src/hooks/useKnockoutExplorer.ts +++ b/src/hooks/useKnockoutExplorer.ts @@ -86,7 +86,7 @@ export function useKnockoutExplorer(platform: Platform): Explorer { let explorer: Explorer; if (platform === Platform.Hosted) { explorer = await configureHosted(); - } else if (platform === Platform.Emulator) { + } else if (platform === Platform.Emulator || platform === Platform.VNextEmulator) { explorer = configureEmulator(); } else if (platform === Platform.Portal) { explorer = await configurePortal(); diff --git a/utils/local-proxy/index.js b/utils/local-proxy/index.js index 906c6080e..565fb6b25 100644 --- a/utils/local-proxy/index.js +++ b/utils/local-proxy/index.js @@ -121,6 +121,7 @@ app.get("/_ready", (_, res) => { const appConf = { PROXY_PATH: "/proxy", EMULATOR_ENDPOINT: conf.EMULATOR_ENDPOINT, + platform: "VNextEmulator", }; app.get("/config.json", (_, res) => { res.status(200).json(appConf).end();