Update to V3 messaging

This commit is contained in:
Laurent Nguyen 2025-02-27 15:28:26 +01:00
parent 36c0bf7392
commit 8cfd951c31
12 changed files with 248 additions and 97 deletions

View File

@ -1,5 +1,6 @@
import * as Cosmos from "@azure/cosmos"; import * as Cosmos from "@azure/cosmos";
import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizationTokenUsingResourceTokens"; import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizationTokenUsingResourceTokens";
import { CosmosDbArtifactType } from "Contracts/FabricMessagesContract";
import { AuthorizationToken } from "Contracts/FabricMessageTypes"; import { AuthorizationToken } from "Contracts/FabricMessageTypes";
import { checkDatabaseResourceTokensValidity, isFabricMirrored } from "Platform/Fabric/FabricUtil"; import { checkDatabaseResourceTokensValidity, isFabricMirrored } from "Platform/Fabric/FabricUtil";
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
@ -8,7 +9,7 @@ import { AuthType } from "../AuthType";
import { BackendApi, PriorityLevel } from "../Common/Constants"; import { BackendApi, PriorityLevel } from "../Common/Constants";
import * as Logger from "../Common/Logger"; import * as Logger from "../Common/Logger";
import { Platform, configContext } from "../ConfigContext"; import { Platform, configContext } from "../ConfigContext";
import { updateUserContext, userContext } from "../UserContext"; import { FabricArtifactInfo, updateUserContext, userContext } from "../UserContext";
import { logConsoleError } from "../Utils/NotificationConsoleUtils"; import { logConsoleError } from "../Utils/NotificationConsoleUtils";
import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils"; import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils";
import { EmulatorMasterKey, HttpHeaders } from "./Constants"; import { EmulatorMasterKey, HttpHeaders } from "./Constants";
@ -54,8 +55,13 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
// User resource tokens // User resource tokens
// TODO userContext.fabricContext.databaseConnectionInfo can be undefined // TODO userContext.fabricContext.databaseConnectionInfo can be undefined
headers[HttpHeaders.msDate] = new Date().toUTCString(); headers[HttpHeaders.msDate] = new Date().toUTCString();
const resourceTokens = userContext.fabricContext.mirroredConnectionInfo.resourceTokens; const resourceTokens = (
checkDatabaseResourceTokensValidity(userContext.fabricContext.mirroredConnectionInfo.resourceTokensTimestamp); userContext.fabricContext.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY]
).resourceTokenInfo.resourceTokens;
checkDatabaseResourceTokensValidity(
(userContext.fabricContext.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY])
.resourceTokenInfo.resourceTokensTimestamp,
);
return getAuthorizationTokenUsingResourceTokens(resourceTokens, requestInfo.path, requestInfo.resourceId); return getAuthorizationTokenUsingResourceTokens(resourceTokens, requestInfo.path, requestInfo.resourceId);
case Cosmos.ResourceType.none: case Cosmos.ResourceType.none:
@ -66,7 +72,9 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
// For now, these operations aren't used, so fetching the authorization token is commented out. // For now, these operations aren't used, so fetching the authorization token is commented out.
// This provider must return a real token to pass validation by the client, so we return the cached resource token // This provider must return a real token to pass validation by the client, so we return the cached resource token
// (which is a valid token, but won't work for these operations). // (which is a valid token, but won't work for these operations).
const resourceTokens2 = userContext.fabricContext.mirroredConnectionInfo.resourceTokens; const resourceTokens2 = (
userContext.fabricContext.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY]
).resourceTokenInfo.resourceTokens;
return getAuthorizationTokenUsingResourceTokens(resourceTokens2, requestInfo.path, requestInfo.resourceId); return getAuthorizationTokenUsingResourceTokens(resourceTokens2, requestInfo.path, requestInfo.resourceId);
/* ************** TODO: Uncomment this code if we need to support these operations ************** /* ************** TODO: Uncomment this code if we need to support these operations **************

View File

@ -1,9 +1,10 @@
import { ContainerResponse } from "@azure/cosmos"; import { ContainerResponse } from "@azure/cosmos";
import { Queries } from "Common/Constants"; import { Queries } from "Common/Constants";
import { CosmosDbArtifactType } from "Contracts/FabricMessagesContract";
import { isFabricMirrored } from "Platform/Fabric/FabricUtil"; import { isFabricMirrored } from "Platform/Fabric/FabricUtil";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import { userContext } from "../../UserContext"; import { FabricArtifactInfo, userContext } from "../../UserContext";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { listCassandraTables } from "../../Utils/arm/generatedClients/cosmos/cassandraResources"; import { listCassandraTables } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/cosmos/gremlinResources"; import { listGremlinGraphs } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
@ -16,11 +17,13 @@ import { handleError } from "../ErrorHandlingUtils";
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> { export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`); const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
if (isFabricMirrored() && userContext.fabricContext?.mirroredConnectionInfo.databaseId === databaseId) { if (isFabricMirrored() && userContext.fabricContext?.databaseName === databaseId) {
const collections: DataModels.Collection[] = []; const collections: DataModels.Collection[] = [];
const promises: Promise<ContainerResponse>[] = []; const promises: Promise<ContainerResponse>[] = [];
for (const collectionResourceId in userContext.fabricContext.mirroredConnectionInfo.resourceTokens) { for (const collectionResourceId in (
userContext.fabricContext.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY]
).resourceTokenInfo.resourceTokens) {
// Dictionary key looks like this: dbs/SampleDB/colls/Container // Dictionary key looks like this: dbs/SampleDB/colls/Container
const resourceIdObj = collectionResourceId.split("/"); const resourceIdObj = collectionResourceId.split("/");
const tokenDatabaseId = resourceIdObj[1]; const tokenDatabaseId = resourceIdObj[1];

View File

@ -1,7 +1,8 @@
import { CosmosDbArtifactType } from "Contracts/FabricMessagesContract";
import { isFabricMirrored, isFabricNative } from "Platform/Fabric/FabricUtil"; import { isFabricMirrored, isFabricNative } from "Platform/Fabric/FabricUtil";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import { userContext } from "../../UserContext"; import { FabricArtifactInfo, userContext } from "../../UserContext";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/cosmos/cassandraResources"; import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/cosmos/gremlinResources"; import { listGremlinDatabases } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
@ -14,8 +15,13 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
let databases: DataModels.Database[]; let databases: DataModels.Database[];
const clearMessage = logConsoleProgress(`Querying databases`); const clearMessage = logConsoleProgress(`Querying databases`);
if (isFabricMirrored() && userContext.fabricContext?.mirroredConnectionInfo.resourceTokens) { if (
const tokensData = userContext.fabricContext.mirroredConnectionInfo; isFabricMirrored() &&
(userContext.fabricContext?.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY]).resourceTokenInfo
.resourceTokens
) {
const tokensData = (userContext.fabricContext.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY])
.resourceTokenInfo;
const databaseIdsSet = new Set<string>(); // databaseId const databaseIdsSet = new Set<string>(); // databaseId
@ -46,8 +52,8 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
})); }));
clearMessage(); clearMessage();
return databases; return databases;
} else if (isFabricNative() && userContext.fabricContext?.nativeConnectionInfo.databaseName) { } else if (isFabricNative() && userContext.fabricContext?.databaseName) {
const databaseId = userContext.fabricContext.nativeConnectionInfo.databaseName; const databaseId = userContext.fabricContext.databaseName;
databases = [ databases = [
{ {
_rid: "", _rid: "",

View File

@ -1,7 +1,7 @@
import { AuthorizationToken } from "./FabricMessageTypes"; import { AuthorizationToken } from "./FabricMessageTypes";
// This is the version of these messages // This is the version of these messages
export const FABRIC_RPC_VERSION = "2"; export const FABRIC_RPC_VERSION = "FabricMessageV3";
// Fabric to Data Explorer // Fabric to Data Explorer
export type FabricMessageV2 = export type FabricMessageV2 =
@ -16,11 +16,6 @@ export type FabricMessageV2 =
message: { message: {
connectionId: string; connectionId: string;
isVisible: boolean; isVisible: boolean;
isReadOnly: boolean;
artifactType: CosmosDbArtifactType;
// For Native artifacts
nativeConnectionInfo?: FabricNativeDatabaseConnectionInfo;
}; };
} }
| { | {
@ -36,7 +31,41 @@ export type FabricMessageV2 =
message: { message: {
id: string; id: string;
error: string | undefined; error: string | undefined;
data: FabricMirroredDatabaseConnectionInfo | undefined; data: ResourceTokenInfo | undefined;
};
}
| {
type: "explorerVisible";
message: {
visible: boolean;
};
};
export type FabricMessageV3 =
| {
type: "newContainer";
databaseName: string;
}
| {
type: "initialize";
version: string;
id: string;
message: InitializeMessageV3<CosmosDbArtifactType>;
}
| {
type: "authorizationToken";
message: {
id: string;
error: string | undefined;
data: AuthorizationToken | undefined;
};
}
| {
type: "allResourceTokens_v2";
message: {
id: string;
error: string | undefined;
data: ResourceTokenInfo | undefined;
}; };
} }
| { | {
@ -47,27 +76,38 @@ export type FabricMessageV2 =
}; };
export enum CosmosDbArtifactType { export enum CosmosDbArtifactType {
MIRRORED = "MIRRORED", MIRRORED_KEY = "MIRRORED_KEY",
MIRRORED_AAD = "MIRRORED_AAD",
NATIVE = "NATIVE", NATIVE = "NATIVE",
} }
export interface ArtifactConnectionInfo {
[CosmosDbArtifactType.MIRRORED_KEY]: { connectionId: string };
[CosmosDbArtifactType.MIRRORED_AAD]: AccessTokenConnectionInfo;
[CosmosDbArtifactType.NATIVE]: AccessTokenConnectionInfo;
}
export interface FabricNativeDatabaseConnectionInfo { export interface AccessTokenConnectionInfo {
accessToken: string; accessToken: string;
databaseName: string; databaseName: string;
accountEndpoint: string; accountEndpoint: string;
} }
export interface CosmosDBTokenResponse { export interface InitializeMessageV3<T extends CosmosDbArtifactType> {
token: string; connectionId: string;
date: string; isVisible: boolean;
isReadOnly: boolean;
artifactType: T;
artifactConnectionInfo: ArtifactConnectionInfo[T];
} }
export interface CosmosDBConnectionInfoResponse { export interface CosmosDBConnectionInfoResponse {
endpoint: string; endpoint: string;
databaseId: string; databaseId: string;
resourceTokens: Record<string, string>; resourceTokens: Record<string, string> | undefined;
accessToken: string | undefined;
isReadOnly: boolean;
credentialType: "Key" | "OAuth2" | undefined;
} }
export interface FabricMirroredDatabaseConnectionInfo extends CosmosDBConnectionInfoResponse { export interface ResourceTokenInfo extends CosmosDBConnectionInfoResponse {
resourceTokensTimestamp: number; resourceTokensTimestamp: number;
} }

View File

@ -43,7 +43,7 @@ import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils"; import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
import { useSidePanel } from "../hooks/useSidePanel"; import { useSidePanel } from "../hooks/useSidePanel";
import { useTabs } from "../hooks/useTabs"; import { ReactTabKind, useTabs } from "../hooks/useTabs";
import "./ComponentRegisterer"; import "./ComponentRegisterer";
import { DialogProps, useDialog } from "./Controls/Dialog"; import { DialogProps, useDialog } from "./Controls/Dialog";
import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent"; import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent";
@ -187,6 +187,10 @@ export default class Explorer {
useNotebook.getState().setNotebookBasePath(userContext.features.notebookBasePath); useNotebook.getState().setNotebookBasePath(userContext.features.notebookBasePath);
} }
if (isFabricMirrored()) {
useTabs.getState().closeReactTab(ReactTabKind.Home);
}
this.refreshExplorer(); this.refreshExplorer();
} }

View File

@ -133,9 +133,7 @@ const GlobalCommands: React.FC<GlobalCommandsProps> = ({ explorer }) => {
label: `New ${getCollectionName()}`, label: `New ${getCollectionName()}`,
icon: <Add16Regular />, icon: <Add16Regular />,
onClick: () => { onClick: () => {
const databaseId = isFabricNative() const databaseId = isFabricNative() ? userContext.fabricContext?.databaseName : undefined;
? userContext.fabricContext?.nativeConnectionInfo?.databaseName
: undefined;
explorer.onNewCollectionClicked({ databaseId }); explorer.onNewCollectionClicked({ databaseId });
}, },
keyboardAction: KeyboardAction.NEW_COLLECTION, keyboardAction: KeyboardAction.NEW_COLLECTION,

View File

@ -130,9 +130,7 @@ export const FabricHomeScreen: React.FC<SplashScreenProps> = (props: SplashScree
description: "Create a destination container to store your data", description: "Create a destination container to store your data",
icon: <DocumentAddRegular />, icon: <DocumentAddRegular />,
onClick: () => { onClick: () => {
const databaseId = isFabricNative() const databaseId = isFabricNative() ? userContext.fabricContext?.databaseName : undefined;
? userContext.fabricContext?.nativeConnectionInfo?.databaseName
: undefined;
props.explorer.onNewCollectionClicked({ databaseId }); props.explorer.onNewCollectionClicked({ databaseId });
}, },
}, },

View File

@ -342,12 +342,15 @@ describe("Documents tab (noSql API)", () => {
updateConfigContext({ platform: Platform.Fabric }); updateConfigContext({ platform: Platform.Fabric });
updateUserContext({ updateUserContext({
fabricContext: { fabricContext: {
connectionId: "test", databaseName: "database",
mirroredConnectionInfo: undefined, artifactInfo: {
nativeConnectionInfo: undefined, connectionId: "test",
artifactType: CosmosDbArtifactType.MIRRORED, resourceTokenInfo: undefined,
},
artifactType: CosmosDbArtifactType.MIRRORED_KEY,
isReadOnly: true, isReadOnly: true,
isVisible: true, isVisible: true,
fabricClientRpcVersion: "rpcVersion",
}, },
}); });

View File

@ -557,7 +557,9 @@ describe("createDatabaseTreeNodes", () => {
() => { () => {
updateConfigContext({ platform: Platform.Fabric }); updateConfigContext({ platform: Platform.Fabric });
updateUserContext({ updateUserContext({
fabricContext: { artifactType: CosmosDbArtifactType.MIRRORED } as FabricContext, fabricContext: {
artifactType: CosmosDbArtifactType.MIRRORED_KEY,
} as FabricContext<CosmosDbArtifactType>,
}); });
}, },
], ],

View File

@ -1,7 +1,7 @@
import { sendCachedDataMessage } from "Common/MessageHandler"; import { sendCachedDataMessage } from "Common/MessageHandler";
import { configContext, Platform } from "ConfigContext"; import { configContext, Platform } from "ConfigContext";
import { FabricMessageTypes } from "Contracts/FabricMessageTypes"; import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
import { CosmosDbArtifactType, FabricMirroredDatabaseConnectionInfo } from "Contracts/FabricMessagesContract"; import { CosmosDbArtifactType, ResourceTokenInfo } from "Contracts/FabricMessagesContract";
import { updateUserContext, userContext } from "UserContext"; import { updateUserContext, userContext } from "UserContext";
import { logConsoleError } from "Utils/NotificationConsoleUtils"; import { logConsoleError } from "Utils/NotificationConsoleUtils";
@ -19,21 +19,25 @@ const requestDatabaseResourceTokens = async (): Promise<void> => {
lastRequestTimestamp = Date.now(); lastRequestTimestamp = Date.now();
try { try {
const fabricDatabaseConnectionInfo = await sendCachedDataMessage<FabricMirroredDatabaseConnectionInfo>( const resourceTokenInfo = await sendCachedDataMessage<ResourceTokenInfo>(
FabricMessageTypes.GetAllResourceTokens, FabricMessageTypes.GetAllResourceTokens,
[], [],
userContext.fabricContext.connectionId, userContext.fabricContext.artifactInfo.connectionId,
); );
if (!userContext.databaseAccount.properties.documentEndpoint) { if (!userContext.databaseAccount.properties.documentEndpoint) {
userContext.databaseAccount.properties.documentEndpoint = fabricDatabaseConnectionInfo.endpoint; userContext.databaseAccount.properties.documentEndpoint = resourceTokenInfo.endpoint;
} }
updateUserContext({ updateUserContext({
fabricContext: { fabricContext: {
...userContext.fabricContext, ...userContext.fabricContext,
mirroredConnectionInfo: fabricDatabaseConnectionInfo, databaseName: resourceTokenInfo.databaseId,
isReadOnly: true, artifactInfo: {
...userContext.fabricContext.artifactInfo,
resourceTokenInfo,
},
isReadOnly: resourceTokenInfo.isReadOnly ?? userContext.fabricContext.isReadOnly,
}, },
databaseAccount: { ...userContext.databaseAccount }, databaseAccount: { ...userContext.databaseAccount },
}); });
@ -75,7 +79,8 @@ export const checkDatabaseResourceTokensValidity = (tokenTimestamp: number): voi
export const isFabricMirrored = (): boolean => export const isFabricMirrored = (): boolean =>
configContext.platform === Platform.Fabric && configContext.platform === Platform.Fabric &&
userContext.fabricContext?.artifactType === CosmosDbArtifactType.MIRRORED; (userContext.fabricContext?.artifactType === CosmosDbArtifactType.MIRRORED_KEY ||
userContext.fabricContext?.artifactType === CosmosDbArtifactType.MIRRORED_AAD);
export const isFabricNative = (): boolean => export const isFabricNative = (): boolean =>
configContext.platform === Platform.Fabric && userContext.fabricContext?.artifactType === CosmosDbArtifactType.NATIVE; configContext.platform === Platform.Fabric && userContext.fabricContext?.artifactType === CosmosDbArtifactType.NATIVE;

View File

@ -1,8 +1,4 @@
import { import { CosmosDbArtifactType, ResourceTokenInfo } from "Contracts/FabricMessagesContract";
CosmosDbArtifactType,
FabricMirroredDatabaseConnectionInfo,
FabricNativeDatabaseConnectionInfo,
} from "Contracts/FabricMessagesContract";
import { ParsedResourceTokenConnectionString } from "Platform/Hosted/Helpers/ResourceTokenUtils"; import { ParsedResourceTokenConnectionString } from "Platform/Hosted/Helpers/ResourceTokenUtils";
import { Action } from "Shared/Telemetry/TelemetryConstants"; import { Action } from "Shared/Telemetry/TelemetryConstants";
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor"; import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
@ -51,13 +47,21 @@ export interface VCoreMongoConnectionParams {
connectionString: string; connectionString: string;
} }
export interface FabricContext { export interface FabricArtifactInfo {
connectionId: string; [CosmosDbArtifactType.MIRRORED_KEY]: {
connectionId: string;
resourceTokenInfo: ResourceTokenInfo | undefined;
};
[CosmosDbArtifactType.MIRRORED_AAD]: undefined;
[CosmosDbArtifactType.NATIVE]: undefined;
}
export interface FabricContext<T extends CosmosDbArtifactType> {
fabricClientRpcVersion: string;
isReadOnly: boolean; isReadOnly: boolean;
isVisible: boolean; isVisible: boolean;
databaseName: string;
artifactType: CosmosDbArtifactType; artifactType: CosmosDbArtifactType;
mirroredConnectionInfo: FabricMirroredDatabaseConnectionInfo | undefined; artifactInfo: FabricArtifactInfo[T];
nativeConnectionInfo: FabricNativeDatabaseConnectionInfo | undefined;
} }
export type AdminFeedbackControlPolicy = export type AdminFeedbackControlPolicy =
@ -76,7 +80,7 @@ export type AdminFeedbackPolicySettings = {
}; };
export interface UserContext { export interface UserContext {
readonly fabricContext?: FabricContext; readonly fabricContext?: FabricContext<CosmosDbArtifactType>;
readonly authType?: AuthType; readonly authType?: AuthType;
readonly masterKey?: string; readonly masterKey?: string;
readonly subscriptionId?: string; readonly subscriptionId?: string;

View File

@ -3,11 +3,14 @@ import { createUri } from "Common/UrlUtility";
import { DATA_EXPLORER_RPC_VERSION } from "Contracts/DataExplorerMessagesContract"; import { DATA_EXPLORER_RPC_VERSION } from "Contracts/DataExplorerMessagesContract";
import { FabricMessageTypes } from "Contracts/FabricMessageTypes"; import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
import { import {
ArtifactConnectionInfo,
CosmosDbArtifactType, CosmosDbArtifactType,
FABRIC_RPC_VERSION, FABRIC_RPC_VERSION,
FabricMessageV2, FabricMessageV2,
FabricNativeDatabaseConnectionInfo, FabricMessageV3,
InitializeMessageV3,
} from "Contracts/FabricMessagesContract"; } from "Contracts/FabricMessagesContract";
import { useDialog } from "Explorer/Controls/Dialog";
import Explorer from "Explorer/Explorer"; import Explorer from "Explorer/Explorer";
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane"; import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
import { useSelectedNode } from "Explorer/useSelectedNode"; import { useSelectedNode } from "Explorer/useSelectedNode";
@ -49,7 +52,7 @@ import {
} from "../Platform/Hosted/HostedUtils"; } from "../Platform/Hosted/HostedUtils";
import { extractFeatures } from "../Platform/Hosted/extractFeatures"; import { extractFeatures } from "../Platform/Hosted/extractFeatures";
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility"; import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
import { Node, PortalEnv, updateUserContext, userContext } from "../UserContext"; import { FabricArtifactInfo, Node, PortalEnv, updateUserContext, userContext } from "../UserContext";
import { import {
acquireMsalTokenForAccount, acquireMsalTokenForAccount,
acquireTokenWithMsal, acquireTokenWithMsal,
@ -109,7 +112,7 @@ export function useKnockoutExplorer(platform: Platform): Explorer {
async function configureFabric(): Promise<Explorer> { async function configureFabric(): Promise<Explorer> {
// These are the versions of Fabric that Data Explorer supports. // These are the versions of Fabric that Data Explorer supports.
const SUPPORTED_FABRIC_VERSIONS = [FABRIC_RPC_VERSION]; const SUPPORTED_FABRIC_VERSIONS = ["2", FABRIC_RPC_VERSION];
let firstContainerOpened = false; let firstContainerOpened = false;
let explorer: Explorer; let explorer: Explorer;
@ -125,7 +128,7 @@ async function configureFabric(): Promise<Explorer> {
return; return;
} }
const data: FabricMessageV2 = event.data?.data; const data: FabricMessageV2 | FabricMessageV3 = event.data?.data;
if (!data) { if (!data) {
return; return;
} }
@ -134,30 +137,51 @@ async function configureFabric(): Promise<Explorer> {
case "initialize": { case "initialize": {
const fabricVersion = data.version; const fabricVersion = data.version;
if (!SUPPORTED_FABRIC_VERSIONS.includes(fabricVersion)) { if (!SUPPORTED_FABRIC_VERSIONS.includes(fabricVersion)) {
// TODO Surface error to user // TODO Surface error to user and log to telemetry
useDialog
.getState()
.showOkModalDialog("Unsupported Fabric version", `Unsupported Fabric version: ${fabricVersion}`);
Logger.logError(`Unsupported Fabric version: ${fabricVersion}`, "Explorer/configureFabric");
console.error(`Unsupported Fabric version: ${fabricVersion}`); console.error(`Unsupported Fabric version: ${fabricVersion}`);
return; return;
} }
// TODO: Default values points to MIRRORED. This is for old fabric clients to explitly settings these values if (fabricVersion === "2") {
data.message.artifactType = data.message.artifactType ?? CosmosDbArtifactType.MIRRORED; // ----------------- TODO: Remove this when FabricMessageV2 is deprecated -----------------
data.message.isReadOnly = data.message.isReadOnly ?? true; const initializationMessage = data.message as {
connectionId: string;
isVisible: boolean;
};
explorer = createExplorerFabric(data.message); explorer = createExplorerFabricLegacy(initializationMessage, data.version);
if (data.message.artifactType === CosmosDbArtifactType.MIRRORED) {
// Do not show Home tab for Mirrored
useTabs.getState().closeReactTab(ReactTabKind.Home);
await scheduleRefreshDatabaseResourceToken(true); await scheduleRefreshDatabaseResourceToken(true);
resolve(explorer);
await explorer.refreshAllDatabases();
if (userContext.fabricContext.isVisible) {
firstContainerOpened = true;
openFirstContainer(explorer, userContext.fabricContext.databaseName);
}
// -----------------------------------------------------------------------------------------
} else if (fabricVersion === FABRIC_RPC_VERSION) {
const initializationMessage = data.message as InitializeMessageV3<CosmosDbArtifactType>;
explorer = createExplorerFabric(initializationMessage, data.version);
if (initializationMessage.artifactType === CosmosDbArtifactType.MIRRORED_KEY) {
// Do not show Home tab for Mirrored
useTabs.getState().closeReactTab(ReactTabKind.Home);
await scheduleRefreshDatabaseResourceToken(true);
}
resolve(explorer);
await explorer.refreshAllDatabases();
const { databaseName } = userContext.fabricContext;
if (userContext.fabricContext.isVisible && databaseName) {
firstContainerOpened = true;
openFirstContainer(explorer, databaseName);
}
} }
resolve(explorer);
await explorer.refreshAllDatabases();
if (userContext.fabricContext.isVisible && userContext.fabricContext.mirroredConnectionInfo?.databaseId) {
firstContainerOpened = true;
openFirstContainer(explorer, userContext.fabricContext.mirroredConnectionInfo.databaseId);
}
break; break;
} }
case "newContainer": case "newContainer":
@ -170,13 +194,12 @@ async function configureFabric(): Promise<Explorer> {
} }
case "explorerVisible": { case "explorerVisible": {
userContext.fabricContext.isVisible = data.message.visible; userContext.fabricContext.isVisible = data.message.visible;
if ( if (userContext.fabricContext.isVisible && !firstContainerOpened) {
userContext.fabricContext.isVisible && const { databaseName } = userContext.fabricContext;
!firstContainerOpened && if (databaseName !== undefined) {
userContext?.fabricContext?.mirroredConnectionInfo?.databaseId !== undefined firstContainerOpened = true;
) { openFirstContainer(explorer, databaseName);
firstContainerOpened = true; }
openFirstContainer(explorer, userContext.fabricContext.mirroredConnectionInfo.databaseId);
} }
break; break;
} }
@ -431,25 +454,67 @@ function configureHostedWithResourceToken(config: ResourceToken): Explorer {
return explorer; return explorer;
} }
const createExplorerFabric = (params: { /**
connectionId: string; * Initialization for FabricMessageV2
isVisible: boolean; * TODO: delete when FabricMessageV2 is deprecated
isReadOnly?: boolean; * @param params
artifactType?: CosmosDbArtifactType; * @returns
nativeConnectionInfo?: FabricNativeDatabaseConnectionInfo; */
}): Explorer => { function createExplorerFabricLegacy(
params: { connectionId: string; isVisible: boolean },
fabricClientRpcVersion: string,
): Explorer {
const artifactInfo: FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY] = {
connectionId: params.connectionId,
resourceTokenInfo: undefined,
};
updateUserContext({ updateUserContext({
fabricContext: { fabricContext: {
connectionId: params.connectionId, fabricClientRpcVersion,
mirroredConnectionInfo: undefined, // Set with resource token response isReadOnly: true,
isReadOnly: params.isReadOnly, isVisible: params.isVisible ?? true,
databaseName: undefined,
artifactType: CosmosDbArtifactType.MIRRORED_KEY,
artifactInfo,
},
authType: AuthType.ConnectionString,
databaseAccount: {
id: "",
location: "",
type: "",
name: "Mounted",
kind: AccountKind.Default,
properties: {
documentEndpoint: undefined,
},
},
});
const explorer = new Explorer();
return explorer;
}
/**
* Initialization for FabricMessageV3 and above
* @param params
* @returns
*/
const createExplorerFabric = (
params: InitializeMessageV3<CosmosDbArtifactType>,
fabricClientRpcVersion: string,
): Explorer => {
updateUserContext({
fabricContext: {
fabricClientRpcVersion,
databaseName: undefined,
isVisible: params.isVisible, isVisible: params.isVisible,
isReadOnly: params.isReadOnly,
artifactType: params.artifactType, artifactType: params.artifactType,
nativeConnectionInfo: params.nativeConnectionInfo, artifactInfo: undefined,
}, },
}); });
if (params.artifactType === CosmosDbArtifactType.MIRRORED) { if (params.artifactType === CosmosDbArtifactType.MIRRORED_KEY) {
updateUserContext({ updateUserContext({
authType: AuthType.ConnectionString, // TODO: will need its own type and Mirroring could be using AAD authType: AuthType.ConnectionString, // TODO: will need its own type and Mirroring could be using AAD
databaseAccount: { databaseAccount: {
@ -462,8 +527,17 @@ const createExplorerFabric = (params: {
documentEndpoint: undefined, documentEndpoint: undefined,
}, },
}, },
fabricContext: {
...userContext.fabricContext,
artifactInfo: {
connectionId: (params.artifactConnectionInfo as ArtifactConnectionInfo[CosmosDbArtifactType.MIRRORED_KEY])
.connectionId,
resourceTokenInfo: undefined,
},
},
}); });
} else if (params.artifactType === CosmosDbArtifactType.NATIVE) { } else if (params.artifactType === CosmosDbArtifactType.NATIVE) {
const nativeParams = params as InitializeMessageV3<CosmosDbArtifactType.NATIVE>;
// Make it behave like Hosted/AAD/RBAC // Make it behave like Hosted/AAD/RBAC
updateUserContext({ updateUserContext({
databaseAccount: { databaseAccount: {
@ -473,14 +547,20 @@ const createExplorerFabric = (params: {
name: "Native", // TODO: not used? name: "Native", // TODO: not used?
kind: AccountKind.Default, kind: AccountKind.Default,
properties: { properties: {
documentEndpoint: params.nativeConnectionInfo.accountEndpoint, documentEndpoint: nativeParams.artifactConnectionInfo.accountEndpoint,
}, },
}, },
authType: AuthType.AAD, authType: AuthType.AAD,
dataPlaneRbacEnabled: true, dataPlaneRbacEnabled: true,
aadToken: params.nativeConnectionInfo.accessToken, aadToken: nativeParams.artifactConnectionInfo.accessToken,
masterKey: undefined, masterKey: undefined,
fabricContext: {
...userContext.fabricContext,
databaseName: nativeParams.artifactConnectionInfo.databaseName,
},
}); });
} else if (params.artifactType === CosmosDbArtifactType.MIRRORED_AAD) {
throw new Error("Mirrored AAD is not supported");
} }
const explorer = new Explorer(); const explorer = new Explorer();