Added VNext feature matrix

This commit is contained in:
Sung-Hyun Kang 2025-08-14 20:46:45 -05:00
parent d385764027
commit 82f50f6b1f
9 changed files with 160 additions and 14 deletions

View File

@ -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<ConfigContext> {
case Platform.Fabric:
case Platform.Hosted:
case Platform.Emulator:
case Platform.VNextEmulator:
updateConfigContext({ platform });
}
}

View File

@ -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<SettingsComponentProps, S
this.saveSettingsButton = {
isEnabled: this.isSaveSettingsButtonEnabled,
isVisible: () => {
return true;
return isFeatureSupported(PlatformFeature.UpdateCollection);
},
};
this.discardSettingsChangesButton = {
isEnabled: this.isDiscardSettingsButtonEnabled,
isVisible: () => {
return true;
return isFeatureSupported(PlatformFeature.UpdateCollection);
},
};

View File

@ -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<void> {
if (
!isFeatureSupported(PlatformFeature.Copilot) ||
userContext.apiType !== "SQL" ||
!userContext.subscriptionId ||
![Environment.Development, Environment.Mpac, Environment.Prod].includes(getEnvironment())

View File

@ -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,9 +64,11 @@ export function createStaticCommandBarButtons(
}
if (userContext.apiType !== "Gremlin") {
const addVsCode = createOpenVsCodeDialogButton(container);
if (addVsCode) {
buttons.push(addVsCode);
}
}
}
if (isDataplaneRbacSupported(userContext.apiType)) {
const [loginButtonProps, setLoginButtonProps] = useState<CommandButtonComponentProps | undefined>(undefined);
@ -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,

View File

@ -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<AddCollectionPanelProps,
}
private shouldShowVectorSearchParameters() {
return isVectorSearchEnabled() && (isServerlessAccount() || this.shouldShowCollectionThroughputInput());
return (
isFeatureSupported(PlatformFeature.VectorSearch) &&
isVectorSearchEnabled() &&
(isServerlessAccount() || this.shouldShowCollectionThroughputInput())
);
}
private shouldShowFullTextSearchParameters() {
return !isFabricNative() && this.showFullTextSearch;
return isFeatureSupported(PlatformFeature.FullTextSearch) && !isFabricNative() && this.showFullTextSearch;
}
private parseUniqueKeys(): DataModels.UniqueKeyPolicy {

View File

@ -6,6 +6,7 @@ import { getFullTextLanguageOptions } from "Explorer/Controls/FullTextSeach/Full
import { isFabricNative } from "Platform/Fabric/FabricUtil";
import React from "react";
import { userContext } from "UserContext";
import { isFeatureSupported, PlatformFeature } from "Utils/PlatformFeatureUtils";
export function getPartitionKeyTooltipText(): string {
if (userContext.apiType === "Mongo") {
@ -85,7 +86,11 @@ export function UniqueKeysHeader(): JSX.Element {
}
export function shouldShowAnalyticalStoreOptions(): boolean {
if (isFabricNative() || configContext.platform === Platform.Emulator) {
if (
!isFeatureSupported(PlatformFeature.AnalyticalStore) ||
isFabricNative() ||
configContext.platform === Platform.Emulator
) {
return false;
}

View File

@ -0,0 +1,112 @@
import { Platform, configContext } from "../ConfigContext";
/**
* Feature flags enumeration - centralized feature definitions
*/
export enum PlatformFeature {
// UI/Core Features
Queries = "Queries",
Notebooks = "Notebooks",
SynapseLink = "SynapseLink",
VSCodeIntegration = "VSCodeIntegration",
GlobalSecondaryIndex = "GlobalSecondaryIndex",
DataPlaneRbac = "DataPlaneRbac",
EntraIDLogin = "EntraIDLogin",
EntreIDRbac = "EntreIDRbac",
RetrySettings = "RetrySettings",
GraphAutoVizOption = "GraphAutoVizOption",
CrossPartitionOption = "CrossPartitionOption",
EnhancedQueryControl = "EnhancedQueryControl",
ParallelismOption = "ParallelismOption",
EnableEntraIdRbac = "EnableEntraIdRbac",
PriorityBasedExecution = "PriorityBasedExecution",
RegionSelection = "RegionSelection",
Copilot = "Copilot",
CloudShell = "CloudShell",
ContainerPagination = "ContainerPagination",
FullTextSearch = "FullTextSearch",
VectorSearch = "VectorSearch",
ThroughputBucketing = "ThroughputBucketing",
ComputedProperties = "ComputedProperties",
AnalyticalStore = "AnalyticalStore",
// CRUD Operations - Database
CreateDatabase = "CreateDatabase",
ReadDatabase = "ReadDatabase",
DeleteDatabase = "DeleteDatabase",
// CRUD Operations - Collection
CreateCollection = "CreateCollection",
ReadCollection = "ReadCollection",
UpdateCollection = "UpdateCollection",
DeleteCollection = "DeleteCollection",
// CRUD Operations - Document
CreateDocument = "CreateDocument",
ReadDocument = "ReadDocument",
UpdateDocument = "UpdateDocument",
DeleteDocument = "DeleteDocument",
// Advanced Database Features
StoredProcedures = "StoredProcedures",
UDF = "UDF",
Trigger = "Trigger",
}
/**
* Feature matrix per platform.
* - Only list platforms that have restrictions. If a platform is not present, all features are considered supported.
* - Start with VNextEmulator today; add more platforms/flags here later without touching calling code.
*/
const FEATURE_MATRIX: ReadonlyMap<Platform, ReadonlySet<PlatformFeature>> = new Map([
[
Platform.VNextEmulator,
new Set<PlatformFeature>([
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)
);
};

View File

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

View File

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