mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-20 01:11:25 +00:00
Initial implementation of two-way communication with Fabric host (#1622)
* Listen to iframe messages. Test posting message. * Plug new container message to show New Container dialog * Rename message action to type * Fix format * Fix format * Remove console.log() statement * Rework fabric init flow. Implement open Collection Tab from fabric. * Rename method to better match its purpose * Update src/hooks/useKnockoutExplorer.ts Use connectionString from message Co-authored-by: Vsevolod Kukol <sevoku@microsoft.com> * Fix format * For openTab action open first collection if not specified. Clean up FabricContract. * Reformat FabricContracts * Highlight current node selection using them token * Reformat * Automatically expand nodes in resource tree if underlying database or collection is expanded. Fix AllowedOrigins. Cleanup code. * Fix format * Fix lint issue * Don't show the home screen for Fabric (#1636) * Fix formatting * Database name to open can be overridden by value in session storage --------- Co-authored-by: Vsevolod Kukol <sevoku@microsoft.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { createUri } from "Common/UrlUtility";
|
||||
import { FabricMessage } from "Contracts/FabricContract";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||
import { fetchEncryptedToken } from "Platform/Hosted/Components/ConnectExplorer";
|
||||
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
||||
import { fetchAccessData } from "hooks/usePortalAccessToken";
|
||||
@@ -10,7 +12,7 @@ import { AccountKind, Flights } from "../Common/Constants";
|
||||
import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
|
||||
import { sendMessage, sendReadyMessage } from "../Common/MessageHandler";
|
||||
import { Platform, configContext, updateConfigContext } from "../ConfigContext";
|
||||
import { ActionType, DataExplorerAction } from "../Contracts/ActionContracts";
|
||||
import { ActionType, DataExplorerAction, TabKind } from "../Contracts/ActionContracts";
|
||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||
import { DataExplorerInputsFrame } from "../Contracts/ViewModels";
|
||||
import { handleOpenAction } from "../Explorer/OpenActions/OpenActions";
|
||||
@@ -63,24 +65,7 @@ export function useKnockoutExplorer(platform: Platform): Explorer {
|
||||
const explorer = await configurePortal();
|
||||
setExplorer(explorer);
|
||||
} else if (platform === Platform.Fabric) {
|
||||
// TODO For now, retrieve info from session storage. Replace with info injected into Data Explorer
|
||||
const connectionString = sessionStorage.getItem("connectionString");
|
||||
if (!connectionString) {
|
||||
console.error("No connection string found in session storage");
|
||||
return;
|
||||
}
|
||||
const encryptedToken = await fetchEncryptedToken(connectionString);
|
||||
// TODO Duplicated from useTokenMetadata
|
||||
const encryptedTokenMetadata = await fetchAccessData(encryptedToken);
|
||||
|
||||
const win = (window as unknown) as HostedExplorerChildFrame;
|
||||
win.hostedConfig = {
|
||||
authType: AuthType.EncryptedToken,
|
||||
encryptedToken,
|
||||
encryptedTokenMetadata,
|
||||
};
|
||||
|
||||
const explorer = await configureHosted();
|
||||
const explorer = await configureFabric();
|
||||
setExplorer(explorer);
|
||||
}
|
||||
}
|
||||
@@ -100,11 +85,104 @@ export function useKnockoutExplorer(platform: Platform): Explorer {
|
||||
return explorer;
|
||||
}
|
||||
|
||||
async function configureFabric(): Promise<Explorer> {
|
||||
let explorer: Explorer;
|
||||
return new Promise<Explorer>((resolve) => {
|
||||
window.addEventListener(
|
||||
"message",
|
||||
async (event) => {
|
||||
if (isInvalidParentFrameOrigin(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shouldProcessMessage(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data: FabricMessage = event.data?.data;
|
||||
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data.type) {
|
||||
case "initialize": {
|
||||
// TODO For now, retrieve info from session storage. Replace with info injected into Data Explorer
|
||||
const connectionString = data.connectionString ?? sessionStorage.getItem("connectionString");
|
||||
if (!connectionString) {
|
||||
console.error("No connection string found in session storage");
|
||||
return undefined;
|
||||
}
|
||||
const encryptedToken = await fetchEncryptedToken(connectionString);
|
||||
// TODO Duplicated from useTokenMetadata
|
||||
const encryptedTokenMetadata = await fetchAccessData(encryptedToken);
|
||||
|
||||
const hostedConfig: EncryptedToken = {
|
||||
authType: AuthType.EncryptedToken,
|
||||
encryptedToken,
|
||||
encryptedTokenMetadata,
|
||||
};
|
||||
|
||||
explorer = await configureWithEncryptedToken(hostedConfig);
|
||||
resolve(explorer);
|
||||
break;
|
||||
}
|
||||
case "newContainer":
|
||||
explorer.onNewCollectionClicked();
|
||||
break;
|
||||
case "openTab": {
|
||||
// Expand database first
|
||||
const databaseName = sessionStorage.getItem("openDatabaseName") ?? data.databaseName;
|
||||
const database = useDatabases.getState().databases.find((db) => db.id() === databaseName);
|
||||
if (database) {
|
||||
await database.expandDatabase();
|
||||
useDatabases.getState().updateDatabase(database);
|
||||
useSelectedNode.getState().setSelectedNode(database);
|
||||
|
||||
let collectionResourceId = data.collectionName;
|
||||
if (collectionResourceId === undefined) {
|
||||
// Pick first collection if collectionName not specified in message
|
||||
collectionResourceId = database.collections()[0]?.id();
|
||||
}
|
||||
|
||||
if (collectionResourceId !== undefined) {
|
||||
// Expand collection
|
||||
const collection = database.collections().find((coll) => coll.id() === collectionResourceId);
|
||||
collection.expandCollection();
|
||||
useSelectedNode.getState().setSelectedNode(collection);
|
||||
|
||||
handleOpenAction(
|
||||
{
|
||||
actionType: ActionType.OpenCollectionTab,
|
||||
databaseResourceId: databaseName,
|
||||
collectionResourceId: data.collectionName,
|
||||
tabKind: TabKind.SQLDocuments,
|
||||
} as DataExplorerAction,
|
||||
useDatabases.getState().databases,
|
||||
explorer
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.error(`Unknown Fabric message type: ${JSON.stringify(data)}`);
|
||||
break;
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
sendReadyMessage();
|
||||
});
|
||||
}
|
||||
|
||||
async function configureHosted(): Promise<Explorer> {
|
||||
const win = (window as unknown) as HostedExplorerChildFrame;
|
||||
let explorer: Explorer;
|
||||
if (win.hostedConfig.authType === AuthType.EncryptedToken) {
|
||||
explorer = configureHostedWithEncryptedToken(win.hostedConfig);
|
||||
explorer = configureWithEncryptedToken(win.hostedConfig);
|
||||
} else if (win.hostedConfig.authType === AuthType.ResourceToken) {
|
||||
explorer = configureHostedWithResourceToken(win.hostedConfig);
|
||||
} else if (win.hostedConfig.authType === AuthType.ConnectionString) {
|
||||
@@ -237,7 +315,7 @@ function configureHostedWithResourceToken(config: ResourceToken): Explorer {
|
||||
return explorer;
|
||||
}
|
||||
|
||||
function configureHostedWithEncryptedToken(config: EncryptedToken): Explorer {
|
||||
function configureWithEncryptedToken(config: EncryptedToken): Explorer {
|
||||
const apiExperience = DefaultExperienceUtility.getDefaultExperienceFromApiKind(config.encryptedTokenMetadata.apiKind);
|
||||
updateUserContext({
|
||||
authType: AuthType.EncryptedToken,
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as ViewModels from "../Contracts/ViewModels";
|
||||
import { CollectionTabKind } from "../Contracts/ViewModels";
|
||||
import NotebookTabV2 from "../Explorer/Tabs/NotebookV2Tab";
|
||||
import TabsBase from "../Explorer/Tabs/TabsBase";
|
||||
import { Platform, configContext } from "./../ConfigContext";
|
||||
|
||||
interface TabsState {
|
||||
openedTabs: TabsBase[];
|
||||
@@ -37,11 +38,22 @@ export enum ReactTabKind {
|
||||
QueryCopilot,
|
||||
}
|
||||
|
||||
// HACK: using this const when the configuration context is not initialized yet.
|
||||
// Since Fabric is always setting the url param, use that instead of the regular config.
|
||||
const isPlatformFabric = (() => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (params.has("platform")) {
|
||||
const platform = params.get("platform");
|
||||
return platform === Platform.Fabric;
|
||||
}
|
||||
return false;
|
||||
})();
|
||||
|
||||
export const useTabs: UseStore<TabsState> = create((set, get) => ({
|
||||
openedTabs: [],
|
||||
openedReactTabs: [ReactTabKind.Home],
|
||||
openedReactTabs: !isPlatformFabric ? [ReactTabKind.Home] : [],
|
||||
activeTab: undefined,
|
||||
activeReactTab: ReactTabKind.Home,
|
||||
activeReactTab: !isPlatformFabric ? ReactTabKind.Home : undefined,
|
||||
networkSettingsWarning: "",
|
||||
queryCopilotTabInitialInput: "",
|
||||
isTabExecuting: false,
|
||||
@@ -92,7 +104,7 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (updatedTabs.length === 0) {
|
||||
if (updatedTabs.length === 0 && configContext.platform !== Platform.Fabric) {
|
||||
set({ activeTab: undefined, activeReactTab: ReactTabKind.Home });
|
||||
}
|
||||
|
||||
@@ -130,7 +142,7 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
|
||||
}
|
||||
});
|
||||
|
||||
if (get().openedTabs.length === 0) {
|
||||
if (get().openedTabs.length === 0 && configContext.platform !== Platform.Fabric) {
|
||||
set({ activeTab: undefined, activeReactTab: ReactTabKind.Home });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user