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:
parent
dfdb44bdc9
commit
d9e142d7a6
File diff suppressed because it is too large
Load Diff
|
@ -13,7 +13,7 @@
|
||||||
"@babel/plugin-proposal-class-properties": "7.12.1",
|
"@babel/plugin-proposal-class-properties": "7.12.1",
|
||||||
"@babel/plugin-proposal-decorators": "7.12.12",
|
"@babel/plugin-proposal-decorators": "7.12.12",
|
||||||
"@fluentui/react": "8.14.3",
|
"@fluentui/react": "8.14.3",
|
||||||
"@fluentui/react-components": "9.30.1",
|
"@fluentui/react-components": "9.32.1",
|
||||||
"@jupyterlab/services": "6.0.2",
|
"@jupyterlab/services": "6.0.2",
|
||||||
"@jupyterlab/terminal": "3.0.3",
|
"@jupyterlab/terminal": "3.0.3",
|
||||||
"@microsoft/applicationinsights-web": "2.6.1",
|
"@microsoft/applicationinsights-web": "2.6.1",
|
||||||
|
@ -236,4 +236,4 @@
|
||||||
"printWidth": 120,
|
"printWidth": 120,
|
||||||
"endOfLine": "auto"
|
"endOfLine": "auto"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,9 @@ let configContext: Readonly<ConfigContext> = {
|
||||||
`^https:\\/\\/[\\.\\w]*ext\\.azure\\.(com|cn|us)$`,
|
`^https:\\/\\/[\\.\\w]*ext\\.azure\\.(com|cn|us)$`,
|
||||||
`^https:\\/\\/[\\.\\w]*\\.ext\\.microsoftazure\\.de$`,
|
`^https:\\/\\/[\\.\\w]*\\.ext\\.microsoftazure\\.de$`,
|
||||||
`^https:\\/\\/cosmos-db-dataexplorer-germanycentral\\.azurewebsites\\.de$`,
|
`^https:\\/\\/cosmos-db-dataexplorer-germanycentral\\.azurewebsites\\.de$`,
|
||||||
|
`^https:\\/\\/.*\\.fabric\\.microsoft\\.com$`,
|
||||||
|
`^https:\\/\\/.*\\.powerbi\\.com$`,
|
||||||
|
`^https:\\/\\/.*\\.analysis-df\\.net$`,
|
||||||
], // Webpack injects this at build time
|
], // Webpack injects this at build time
|
||||||
gitSha: process.env.GIT_SHA,
|
gitSha: process.env.GIT_SHA,
|
||||||
hostedExplorerURL: "https://cosmos.azure.com/",
|
hostedExplorerURL: "https://cosmos.azure.com/",
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
export type FabricMessage =
|
||||||
|
| {
|
||||||
|
type: "newContainer";
|
||||||
|
databaseName: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "initialize";
|
||||||
|
connectionString: string | undefined;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "openTab";
|
||||||
|
databaseName: string;
|
||||||
|
collectionName: string | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DataExploreMessage =
|
||||||
|
| "ready"
|
||||||
|
| {
|
||||||
|
type: number;
|
||||||
|
data: {
|
||||||
|
action: "LoadDatabases";
|
||||||
|
actionModifier: "success" | "start";
|
||||||
|
defaultExperience: "SQL";
|
||||||
|
};
|
||||||
|
};
|
|
@ -9,8 +9,11 @@ import {
|
||||||
Tree,
|
Tree,
|
||||||
TreeItem,
|
TreeItem,
|
||||||
TreeItemLayout,
|
TreeItemLayout,
|
||||||
|
TreeOpenChangeData,
|
||||||
|
TreeOpenChangeEvent,
|
||||||
} from "@fluentui/react-components";
|
} from "@fluentui/react-components";
|
||||||
import { MoreHorizontal20Regular } from "@fluentui/react-icons";
|
import { MoreHorizontal20Regular } from "@fluentui/react-icons";
|
||||||
|
import { tokens } from "@fluentui/react-theme";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
export interface TreeNode2MenuItem {
|
export interface TreeNode2MenuItem {
|
||||||
|
@ -27,7 +30,7 @@ export interface TreeNode2 {
|
||||||
children?: TreeNode2[];
|
children?: TreeNode2[];
|
||||||
contextMenu?: TreeNode2MenuItem[];
|
contextMenu?: TreeNode2MenuItem[];
|
||||||
iconSrc?: string;
|
iconSrc?: string;
|
||||||
// isExpanded?: boolean;
|
isExpanded?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
isAlphaSorted?: boolean;
|
isAlphaSorted?: boolean;
|
||||||
// data?: any; // Piece of data corresponding to this node
|
// data?: any; // Piece of data corresponding to this node
|
||||||
|
@ -37,7 +40,7 @@ export interface TreeNode2 {
|
||||||
isScrollable?: boolean;
|
isScrollable?: boolean;
|
||||||
isSelected?: () => boolean;
|
isSelected?: () => boolean;
|
||||||
onClick?: () => void; // Only if a leaf, other click will expand/collapse
|
onClick?: () => void; // Only if a leaf, other click will expand/collapse
|
||||||
onExpanded?: () => void;
|
onExpanded?: () => Promise<void>;
|
||||||
onCollapsed?: () => void;
|
onCollapsed?: () => void;
|
||||||
onContextMenuOpen?: () => void;
|
onContextMenuOpen?: () => void;
|
||||||
}
|
}
|
||||||
|
@ -46,7 +49,6 @@ export interface TreeNode2ComponentProps {
|
||||||
node: TreeNode2;
|
node: TreeNode2;
|
||||||
className?: string;
|
className?: string;
|
||||||
treeNodeId: string;
|
treeNodeId: string;
|
||||||
globalOpenIds: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTreeIcon = (iconSrc: string): JSX.Element => <img src={iconSrc} alt="" style={{ width: 20, height: 20 }} />;
|
const getTreeIcon = (iconSrc: string): JSX.Element => <img src={iconSrc} alt="" style={{ width: 20, height: 20 }} />;
|
||||||
|
@ -54,20 +56,8 @@ const getTreeIcon = (iconSrc: string): JSX.Element => <img src={iconSrc} alt=""
|
||||||
export const TreeNode2Component: React.FC<TreeNode2ComponentProps> = ({
|
export const TreeNode2Component: React.FC<TreeNode2ComponentProps> = ({
|
||||||
node,
|
node,
|
||||||
treeNodeId,
|
treeNodeId,
|
||||||
globalOpenIds,
|
|
||||||
}: TreeNode2ComponentProps): JSX.Element => {
|
}: TreeNode2ComponentProps): JSX.Element => {
|
||||||
// const defaultOpenItems = node.isExpanded ? children?.map((child: TreeNode2) => child.label) : undefined;
|
const [isLoading, setIsLoading] = React.useState<boolean>(false);
|
||||||
const [isExpanded, setIsExpanded] = React.useState<boolean>(false);
|
|
||||||
|
|
||||||
// Compute whether node is expanded
|
|
||||||
React.useEffect(() => {
|
|
||||||
const isNowExpanded = globalOpenIds && globalOpenIds.includes(treeNodeId);
|
|
||||||
if (!isExpanded && isNowExpanded) {
|
|
||||||
// Catch the transition non-expanded to expanded
|
|
||||||
node.onExpanded?.();
|
|
||||||
}
|
|
||||||
setIsExpanded(isNowExpanded);
|
|
||||||
}, [globalOpenIds, treeNodeId, node, isExpanded]);
|
|
||||||
|
|
||||||
const getSortedChildren = (treeNode: TreeNode2): TreeNode2[] => {
|
const getSortedChildren = (treeNode: TreeNode2): TreeNode2[] => {
|
||||||
if (!treeNode || !treeNode.children) {
|
if (!treeNode || !treeNode.children) {
|
||||||
|
@ -95,8 +85,24 @@ export const TreeNode2Component: React.FC<TreeNode2ComponentProps> = ({
|
||||||
return unsortedChildren;
|
return unsortedChildren;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onOpenChange = (_: TreeOpenChangeEvent, data: TreeOpenChangeData) => {
|
||||||
|
if (!node.isExpanded && data.open && node.onExpanded) {
|
||||||
|
// Catch the transition non-expanded to expanded
|
||||||
|
setIsLoading(true);
|
||||||
|
node.onExpanded?.().then(() => setIsLoading(false));
|
||||||
|
} else if (node.isExpanded && !data.open && node.onCollapsed) {
|
||||||
|
// Catch the transition expanded to non-expanded
|
||||||
|
node.onCollapsed?.();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TreeItem value={treeNodeId} itemType={node.children !== undefined ? "branch" : "leaf"} style={{ height: "100%" }}>
|
<TreeItem
|
||||||
|
value={treeNodeId}
|
||||||
|
itemType={node.children !== undefined ? "branch" : "leaf"}
|
||||||
|
style={{ height: "100%" }}
|
||||||
|
onOpenChange={onOpenChange}
|
||||||
|
>
|
||||||
<TreeItemLayout
|
<TreeItemLayout
|
||||||
className={node.className}
|
className={node.className}
|
||||||
actions={
|
actions={
|
||||||
|
@ -117,22 +123,21 @@ export const TreeNode2Component: React.FC<TreeNode2ComponentProps> = ({
|
||||||
</Menu>
|
</Menu>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
expandIcon={node.isLoading ? <Spinner size="extra-tiny" /> : undefined}
|
expandIcon={isLoading ? <Spinner size="extra-tiny" /> : undefined}
|
||||||
iconBefore={node.iconSrc && getTreeIcon(node.iconSrc)}
|
iconBefore={node.iconSrc && getTreeIcon(node.iconSrc)}
|
||||||
|
style={{
|
||||||
|
backgroundColor: node.isSelected && node.isSelected() ? tokens.colorNeutralBackground1Selected : undefined,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<span onClick={() => node.onClick?.()}>{node.label}</span>
|
<span onClick={() => node.onClick?.()}>{node.label}</span>
|
||||||
</TreeItemLayout>
|
</TreeItemLayout>
|
||||||
{!node.isLoading && node.children?.length > 0 && (
|
{!node.isLoading && node.children?.length > 0 && (
|
||||||
<Tree
|
<Tree style={{ overflow: node.isScrollable ? "auto" : undefined }}>
|
||||||
// defaultOpenItems={defaultOpenItems}
|
|
||||||
style={{ overflow: node.isScrollable ? "auto" : undefined }}
|
|
||||||
>
|
|
||||||
{getSortedChildren(node).map((childNode: TreeNode2) => (
|
{getSortedChildren(node).map((childNode: TreeNode2) => (
|
||||||
<TreeNode2Component
|
<TreeNode2Component
|
||||||
key={childNode.label}
|
key={childNode.label}
|
||||||
node={childNode}
|
node={childNode}
|
||||||
treeNodeId={`${treeNodeId}/${childNode.label}`}
|
treeNodeId={`${treeNodeId}/${childNode.label}`}
|
||||||
globalOpenIds={globalOpenIds}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Tree>
|
</Tree>
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
TreeOpenChangeEvent,
|
TreeOpenChangeEvent,
|
||||||
createLightTheme,
|
createLightTheme,
|
||||||
} from "@fluentui/react-components";
|
} from "@fluentui/react-components";
|
||||||
import { TreeNode2Component } from "Explorer/Controls/TreeComponent2/TreeNode2Component";
|
import { TreeNode2, TreeNode2Component } from "Explorer/Controls/TreeComponent2/TreeNode2Component";
|
||||||
import { useDatabaseTreeNodes } from "Explorer/Tree2/useDatabaseTreeNodes";
|
import { useDatabaseTreeNodes } from "Explorer/Tree2/useDatabaseTreeNodes";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import shallow from "zustand/shallow";
|
import shallow from "zustand/shallow";
|
||||||
|
@ -69,16 +69,45 @@ export const ResourceTree2: React.FC<ResourceTreeProps> = ({ container }: Resour
|
||||||
);
|
);
|
||||||
// const { activeTab } = useTabs();
|
// const { activeTab } = useTabs();
|
||||||
const databaseTreeNodes = useDatabaseTreeNodes(container, isNotebookEnabled);
|
const databaseTreeNodes = useDatabaseTreeNodes(container, isNotebookEnabled);
|
||||||
const dataNodeTree = {
|
const [openItems, setOpenItems] = React.useState<Iterable<TreeItemValue>>([DATA_TREE_LABEL]);
|
||||||
|
|
||||||
|
const dataNodeTree: TreeNode2 = {
|
||||||
id: "data",
|
id: "data",
|
||||||
label: DATA_TREE_LABEL,
|
label: DATA_TREE_LABEL,
|
||||||
isExpanded: true,
|
|
||||||
className: "accordionItemHeader",
|
className: "accordionItemHeader",
|
||||||
children: databaseTreeNodes,
|
children: databaseTreeNodes,
|
||||||
isScrollable: true,
|
isScrollable: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const [openItems, setOpenItems] = React.useState<Iterable<TreeItemValue>>([DATA_TREE_LABEL]);
|
React.useEffect(() => {
|
||||||
|
// Compute open items based on node.isExpanded
|
||||||
|
const updateOpenItems = (node: TreeNode2, parentNodeId: string): void => {
|
||||||
|
// This will look for ANY expanded node, event if its parent node isn't expanded
|
||||||
|
// and add it to the openItems list
|
||||||
|
const globalId = parentNodeId === undefined ? node.label : `${parentNodeId}/${node.label}`;
|
||||||
|
|
||||||
|
if (node.isExpanded) {
|
||||||
|
let found = false;
|
||||||
|
for (const id of openItems) {
|
||||||
|
if (id === globalId) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
setOpenItems((prevOpenItems) => [...prevOpenItems, globalId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.children) {
|
||||||
|
for (const child of node.children) {
|
||||||
|
updateOpenItems(child, globalId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateOpenItems(dataNodeTree, undefined);
|
||||||
|
}, [databaseTreeNodes]);
|
||||||
|
|
||||||
const handleOpenChange = (event: TreeOpenChangeEvent, data: TreeOpenChangeData) => setOpenItems(data.openItems);
|
const handleOpenChange = (event: TreeOpenChangeEvent, data: TreeOpenChangeData) => setOpenItems(data.openItems);
|
||||||
|
|
||||||
|
@ -93,13 +122,7 @@ export const ResourceTree2: React.FC<ResourceTreeProps> = ({ container }: Resour
|
||||||
style={{ height: "100%", width: "290px" }}
|
style={{ height: "100%", width: "290px" }}
|
||||||
>
|
>
|
||||||
{[dataNodeTree].map((node) => (
|
{[dataNodeTree].map((node) => (
|
||||||
<TreeNode2Component
|
<TreeNode2Component key={node.label} className="dataResourceTree" node={node} treeNodeId={node.label} />
|
||||||
key={node.label}
|
|
||||||
className="dataResourceTree"
|
|
||||||
node={node}
|
|
||||||
treeNodeId={node.label}
|
|
||||||
globalOpenIds={[...openItems].map((item) => item.toString())}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</Tree>
|
</Tree>
|
||||||
</FluentProvider>
|
</FluentProvider>
|
||||||
|
|
|
@ -45,7 +45,7 @@ export const buildCollectionNode = (
|
||||||
// push to most recent
|
// push to most recent
|
||||||
mostRecentActivity.collectionWasOpened(userContext.databaseAccount?.id, collection);
|
mostRecentActivity.collectionWasOpened(userContext.databaseAccount?.id, collection);
|
||||||
},
|
},
|
||||||
onExpanded: () => {
|
onExpanded: async () => {
|
||||||
// Rewritten version of expandCollapseCollection
|
// Rewritten version of expandCollapseCollection
|
||||||
useSelectedNode.getState().setSelectedNode(collection);
|
useSelectedNode.getState().setSelectedNode(collection);
|
||||||
useCommandBar.getState().setContextButtons([]);
|
useCommandBar.getState().setContextButtons([]);
|
||||||
|
@ -53,9 +53,16 @@ export const buildCollectionNode = (
|
||||||
(tab: TabsBase) =>
|
(tab: TabsBase) =>
|
||||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||||
);
|
);
|
||||||
|
useDatabases.getState().updateDatabase(database);
|
||||||
},
|
},
|
||||||
isSelected: () => useSelectedNode.getState().isDataNodeSelected(collection.databaseId, collection.id()),
|
isSelected: () => useSelectedNode.getState().isDataNodeSelected(collection.databaseId, collection.id()),
|
||||||
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(collection),
|
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(collection),
|
||||||
|
onCollapsed: () => {
|
||||||
|
collection.collapseCollection();
|
||||||
|
// useCommandBar.getState().setContextButtons([]);
|
||||||
|
useDatabases.getState().updateDatabase(database);
|
||||||
|
},
|
||||||
|
isExpanded: collection.isCollectionExpanded(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -25,15 +25,22 @@ export const useDatabaseTreeNodes = (container: Explorer, isNotebookEnabled: boo
|
||||||
contextMenu: ResourceTreeContextMenuButtonFactory.createDatabaseContextMenu(container, database.id()),
|
contextMenu: ResourceTreeContextMenuButtonFactory.createDatabaseContextMenu(container, database.id()),
|
||||||
onExpanded: async () => {
|
onExpanded: async () => {
|
||||||
useSelectedNode.getState().setSelectedNode(database);
|
useSelectedNode.getState().setSelectedNode(database);
|
||||||
if (databaseNode.children?.length === 0) {
|
if (!databaseNode.children || databaseNode.children?.length === 0) {
|
||||||
databaseNode.isLoading = true;
|
databaseNode.isLoading = true;
|
||||||
}
|
}
|
||||||
await database.expandDatabase();
|
await database.expandDatabase();
|
||||||
databaseNode.isLoading = false;
|
databaseNode.isLoading = false;
|
||||||
useCommandBar.getState().setContextButtons([]);
|
useCommandBar.getState().setContextButtons([]);
|
||||||
refreshActiveTab((tab: TabsBase) => tab.collection?.databaseId === database.id());
|
refreshActiveTab((tab: TabsBase) => tab.collection?.databaseId === database.id());
|
||||||
|
useDatabases.getState().updateDatabase(database);
|
||||||
},
|
},
|
||||||
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(database),
|
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(database),
|
||||||
|
isExpanded: database.isDatabaseExpanded(),
|
||||||
|
onCollapsed: () => {
|
||||||
|
database.collapseDatabase();
|
||||||
|
// useCommandBar.getState().setContextButtons([]);
|
||||||
|
useDatabases.getState().updateDatabase(database);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (database.isDatabaseShared() && configContext.platform !== Platform.Fabric) {
|
if (database.isDatabaseShared() && configContext.platform !== Platform.Fabric) {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { createUri } from "Common/UrlUtility";
|
import { createUri } from "Common/UrlUtility";
|
||||||
|
import { FabricMessage } from "Contracts/FabricContract";
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
import { fetchEncryptedToken } from "Platform/Hosted/Components/ConnectExplorer";
|
import { fetchEncryptedToken } from "Platform/Hosted/Components/ConnectExplorer";
|
||||||
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
||||||
import { fetchAccessData } from "hooks/usePortalAccessToken";
|
import { fetchAccessData } from "hooks/usePortalAccessToken";
|
||||||
|
@ -10,7 +12,7 @@ import { AccountKind, Flights } from "../Common/Constants";
|
||||||
import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
|
import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
|
||||||
import { sendMessage, sendReadyMessage } from "../Common/MessageHandler";
|
import { sendMessage, sendReadyMessage } from "../Common/MessageHandler";
|
||||||
import { Platform, configContext, updateConfigContext } from "../ConfigContext";
|
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 { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
import { DataExplorerInputsFrame } from "../Contracts/ViewModels";
|
import { DataExplorerInputsFrame } from "../Contracts/ViewModels";
|
||||||
import { handleOpenAction } from "../Explorer/OpenActions/OpenActions";
|
import { handleOpenAction } from "../Explorer/OpenActions/OpenActions";
|
||||||
|
@ -63,24 +65,7 @@ export function useKnockoutExplorer(platform: Platform): Explorer {
|
||||||
const explorer = await configurePortal();
|
const explorer = await configurePortal();
|
||||||
setExplorer(explorer);
|
setExplorer(explorer);
|
||||||
} else if (platform === Platform.Fabric) {
|
} else if (platform === Platform.Fabric) {
|
||||||
// TODO For now, retrieve info from session storage. Replace with info injected into Data Explorer
|
const explorer = await configureFabric();
|
||||||
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();
|
|
||||||
setExplorer(explorer);
|
setExplorer(explorer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,11 +85,104 @@ export function useKnockoutExplorer(platform: Platform): Explorer {
|
||||||
return 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> {
|
async function configureHosted(): Promise<Explorer> {
|
||||||
const win = (window as unknown) as HostedExplorerChildFrame;
|
const win = (window as unknown) as HostedExplorerChildFrame;
|
||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
if (win.hostedConfig.authType === AuthType.EncryptedToken) {
|
if (win.hostedConfig.authType === AuthType.EncryptedToken) {
|
||||||
explorer = configureHostedWithEncryptedToken(win.hostedConfig);
|
explorer = configureWithEncryptedToken(win.hostedConfig);
|
||||||
} else if (win.hostedConfig.authType === AuthType.ResourceToken) {
|
} else if (win.hostedConfig.authType === AuthType.ResourceToken) {
|
||||||
explorer = configureHostedWithResourceToken(win.hostedConfig);
|
explorer = configureHostedWithResourceToken(win.hostedConfig);
|
||||||
} else if (win.hostedConfig.authType === AuthType.ConnectionString) {
|
} else if (win.hostedConfig.authType === AuthType.ConnectionString) {
|
||||||
|
@ -237,7 +315,7 @@ function configureHostedWithResourceToken(config: ResourceToken): Explorer {
|
||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|
||||||
function configureHostedWithEncryptedToken(config: EncryptedToken): Explorer {
|
function configureWithEncryptedToken(config: EncryptedToken): Explorer {
|
||||||
const apiExperience = DefaultExperienceUtility.getDefaultExperienceFromApiKind(config.encryptedTokenMetadata.apiKind);
|
const apiExperience = DefaultExperienceUtility.getDefaultExperienceFromApiKind(config.encryptedTokenMetadata.apiKind);
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
authType: AuthType.EncryptedToken,
|
authType: AuthType.EncryptedToken,
|
||||||
|
|
|
@ -3,6 +3,7 @@ import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import { CollectionTabKind } from "../Contracts/ViewModels";
|
import { CollectionTabKind } from "../Contracts/ViewModels";
|
||||||
import NotebookTabV2 from "../Explorer/Tabs/NotebookV2Tab";
|
import NotebookTabV2 from "../Explorer/Tabs/NotebookV2Tab";
|
||||||
import TabsBase from "../Explorer/Tabs/TabsBase";
|
import TabsBase from "../Explorer/Tabs/TabsBase";
|
||||||
|
import { Platform, configContext } from "./../ConfigContext";
|
||||||
|
|
||||||
interface TabsState {
|
interface TabsState {
|
||||||
openedTabs: TabsBase[];
|
openedTabs: TabsBase[];
|
||||||
|
@ -37,11 +38,22 @@ export enum ReactTabKind {
|
||||||
QueryCopilot,
|
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) => ({
|
export const useTabs: UseStore<TabsState> = create((set, get) => ({
|
||||||
openedTabs: [],
|
openedTabs: [],
|
||||||
openedReactTabs: [ReactTabKind.Home],
|
openedReactTabs: !isPlatformFabric ? [ReactTabKind.Home] : [],
|
||||||
activeTab: undefined,
|
activeTab: undefined,
|
||||||
activeReactTab: ReactTabKind.Home,
|
activeReactTab: !isPlatformFabric ? ReactTabKind.Home : undefined,
|
||||||
networkSettingsWarning: "",
|
networkSettingsWarning: "",
|
||||||
queryCopilotTabInitialInput: "",
|
queryCopilotTabInitialInput: "",
|
||||||
isTabExecuting: false,
|
isTabExecuting: false,
|
||||||
|
@ -92,7 +104,7 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
if (updatedTabs.length === 0) {
|
if (updatedTabs.length === 0 && configContext.platform !== Platform.Fabric) {
|
||||||
set({ activeTab: undefined, activeReactTab: ReactTabKind.Home });
|
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 });
|
set({ activeTab: undefined, activeReactTab: ReactTabKind.Home });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue