Fabric: clean up RPC and support show/hide toolbar message (#1680)
* Use Promise for allResourceToken fabric message. Cleanup token message handling and add debounce. * Improve rpc and update initalization flow * Fix format * Rev up message names for new version * Refactor RPC with Fabric * Build fix * Fix build * Fix format * Update Message types * Fix format * Fix comments * Fabric toolbar style and support to show/hide it (#1720) * Add Fabric specific Toolbar design * Add Fabric message to show/hide the Toolbar * Fix CommandBarUtil formatting * Update zustand state on setToolbarStatus to trigger a redraw of the command bar with updated visibility --------- Co-authored-by: Laurent Nguyen <laurent.nguyen@microsoft.com> * Fix format --------- Co-authored-by: Vsevolod Kukol <sevoku@microsoft.com>
This commit is contained in:
parent
5d13463bdb
commit
0975591945
|
@ -147,6 +147,7 @@
|
||||||
|
|
||||||
// CommandBar
|
// CommandBar
|
||||||
@CommandBarButtonHeight: 40px;
|
@CommandBarButtonHeight: 40px;
|
||||||
|
@FabricCommandBarButtonHeight: 34px;
|
||||||
|
|
||||||
/**********************************************************************************
|
/**********************************************************************************
|
||||||
Portal Consts
|
Portal Consts
|
||||||
|
@ -164,7 +165,7 @@
|
||||||
@FabricFont: "Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
|
@FabricFont: "Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
|
||||||
|
|
||||||
@FabricBoxBorderRadius: 8px;
|
@FabricBoxBorderRadius: 8px;
|
||||||
@FabricBoxBorderShadow: 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.14);
|
@FabricBoxBorderShadow: rgba(0, 0, 0, 0.133) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, 0.11) 0px 0.3px 0.9px 0px;
|
||||||
@FabricBoxMargin: 4px 3px 4px 3px;
|
@FabricBoxMargin: 4px 3px 4px 3px;
|
||||||
|
|
||||||
@FabricAccentMediumHigh: #0c695a;
|
@FabricAccentMediumHigh: #0c695a;
|
||||||
|
|
|
@ -47,11 +47,15 @@ a:focus {
|
||||||
border-radius: @FabricBoxBorderRadius;
|
border-radius: @FabricBoxBorderRadius;
|
||||||
box-shadow: @FabricBoxBorderShadow;
|
box-shadow: @FabricBoxBorderShadow;
|
||||||
margin: @FabricBoxMargin;
|
margin: @FabricBoxMargin;
|
||||||
|
margin-top: 0px;
|
||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
|
padding: 0px;
|
||||||
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dividerContainer {
|
.dividerContainer {
|
||||||
padding: @SmallSpace 0px @SmallSpace 0px;
|
padding: @SmallSpace 0px @SmallSpace 0px;
|
||||||
|
height: @FabricCommandBarButtonHeight;
|
||||||
.flex-display();
|
.flex-display();
|
||||||
|
|
||||||
span {
|
span {
|
||||||
|
|
|
@ -40,9 +40,10 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
||||||
case Cosmos.ResourceType.item:
|
case Cosmos.ResourceType.item:
|
||||||
case Cosmos.ResourceType.pkranges:
|
case Cosmos.ResourceType.pkranges:
|
||||||
// User resource tokens
|
// User resource tokens
|
||||||
|
// TODO userContext.fabricContext.databaseConnectionInfo can be undefined
|
||||||
headers[HttpHeaders.msDate] = new Date().toUTCString();
|
headers[HttpHeaders.msDate] = new Date().toUTCString();
|
||||||
const resourceTokens = userContext.fabricDatabaseConnectionInfo.resourceTokens;
|
const resourceTokens = userContext.fabricContext.databaseConnectionInfo.resourceTokens;
|
||||||
checkDatabaseResourceTokensValidity(userContext.fabricDatabaseConnectionInfo.resourceTokensTimestamp);
|
checkDatabaseResourceTokensValidity(userContext.fabricContext.databaseConnectionInfo.resourceTokensTimestamp);
|
||||||
return getAuthorizationTokenUsingResourceTokens(resourceTokens, requestInfo.path, requestInfo.resourceId);
|
return getAuthorizationTokenUsingResourceTokens(resourceTokens, requestInfo.path, requestInfo.resourceId);
|
||||||
|
|
||||||
case Cosmos.ResourceType.none:
|
case Cosmos.ResourceType.none:
|
||||||
|
@ -51,9 +52,11 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
||||||
case Cosmos.ResourceType.user:
|
case Cosmos.ResourceType.user:
|
||||||
case Cosmos.ResourceType.permission:
|
case Cosmos.ResourceType.permission:
|
||||||
// User master tokens
|
// User master tokens
|
||||||
const authorizationToken = await sendCachedDataMessage<AuthorizationToken>(MessageTypes.GetAuthorizationToken, [
|
const authorizationToken = await sendCachedDataMessage<AuthorizationToken>(
|
||||||
requestInfo,
|
MessageTypes.GetAuthorizationToken,
|
||||||
]);
|
[requestInfo],
|
||||||
|
userContext.fabricContext.connectionId,
|
||||||
|
);
|
||||||
console.log("Response from Fabric: ", authorizationToken);
|
console.log("Response from Fabric: ", authorizationToken);
|
||||||
headers[HttpHeaders.msDate] = authorizationToken.XDate;
|
headers[HttpHeaders.msDate] = authorizationToken.XDate;
|
||||||
return decodeURIComponent(authorizationToken.PrimaryReadWriteToken);
|
return decodeURIComponent(authorizationToken.PrimaryReadWriteToken);
|
||||||
|
|
|
@ -27,15 +27,24 @@ export function handleCachedDataMessage(message: any): void {
|
||||||
runGarbageCollector();
|
runGarbageCollector();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param messageType
|
||||||
|
* @param params
|
||||||
|
* @param scope Use this string to identify request Useful to distinguish response from different senders
|
||||||
|
* @param timeoutInMs
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
export function sendCachedDataMessage<TResponseDataModel>(
|
export function sendCachedDataMessage<TResponseDataModel>(
|
||||||
messageType: MessageTypes,
|
messageType: MessageTypes,
|
||||||
params: Object[],
|
params: Object[],
|
||||||
|
scope?: string,
|
||||||
timeoutInMs?: number,
|
timeoutInMs?: number,
|
||||||
): Q.Promise<TResponseDataModel> {
|
): Q.Promise<TResponseDataModel> {
|
||||||
let cachedDataPromise: CachedDataPromise<TResponseDataModel> = {
|
let cachedDataPromise: CachedDataPromise<TResponseDataModel> = {
|
||||||
deferred: Q.defer<TResponseDataModel>(),
|
deferred: Q.defer<TResponseDataModel>(),
|
||||||
startTime: new Date(),
|
startTime: new Date(),
|
||||||
id: _.uniqueId(),
|
id: _.uniqueId(scope),
|
||||||
};
|
};
|
||||||
RequestMap[cachedDataPromise.id] = cachedDataPromise;
|
RequestMap[cachedDataPromise.id] = cachedDataPromise;
|
||||||
sendMessage({ type: messageType, params: params, id: cachedDataPromise.id });
|
sendMessage({ type: messageType, params: params, id: cachedDataPromise.id });
|
||||||
|
@ -47,6 +56,10 @@ export function sendCachedDataMessage<TResponseDataModel>(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param data Overwrite the data property of the message
|
||||||
|
*/
|
||||||
export function sendMessage(data: any): void {
|
export function sendMessage(data: any): void {
|
||||||
_sendMessage({
|
_sendMessage({
|
||||||
signature: "pcIframe",
|
signature: "pcIframe",
|
||||||
|
|
|
@ -18,13 +18,13 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
|
||||||
|
|
||||||
if (
|
if (
|
||||||
configContext.platform === Platform.Fabric &&
|
configContext.platform === Platform.Fabric &&
|
||||||
userContext.fabricDatabaseConnectionInfo &&
|
userContext.fabricContext &&
|
||||||
userContext.fabricDatabaseConnectionInfo.databaseId === databaseId
|
userContext.fabricContext.databaseConnectionInfo.databaseId === databaseId
|
||||||
) {
|
) {
|
||||||
const collections: DataModels.Collection[] = [];
|
const collections: DataModels.Collection[] = [];
|
||||||
const promises: Promise<ContainerResponse>[] = [];
|
const promises: Promise<ContainerResponse>[] = [];
|
||||||
|
|
||||||
for (const collectionResourceId in userContext.fabricDatabaseConnectionInfo.resourceTokens) {
|
for (const collectionResourceId in userContext.fabricContext.databaseConnectionInfo.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];
|
||||||
|
|
|
@ -14,8 +14,8 @@ 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 (configContext.platform === Platform.Fabric && userContext.fabricDatabaseConnectionInfo?.resourceTokens) {
|
if (configContext.platform === Platform.Fabric && userContext.fabricContext?.databaseConnectionInfo.resourceTokens) {
|
||||||
const tokensData = userContext.fabricDatabaseConnectionInfo;
|
const tokensData = userContext.fabricContext.databaseConnectionInfo;
|
||||||
|
|
||||||
const databaseIdsSet = new Set<string>(); // databaseId
|
const databaseIdsSet = new Set<string>(); // databaseId
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { MessageTypes } from "./MessageTypes";
|
||||||
|
|
||||||
|
// This is the current version of these messages
|
||||||
|
export const DATA_EXPLORER_RPC_VERSION = "2";
|
||||||
|
|
||||||
|
// Data Explorer to Fabric
|
||||||
|
|
||||||
|
// TODO Remove when upgrading to Fabric v2
|
||||||
|
export type DataExploreMessageV1 =
|
||||||
|
| "ready"
|
||||||
|
| {
|
||||||
|
type: MessageTypes.GetAuthorizationToken;
|
||||||
|
id: string;
|
||||||
|
params: GetCosmosTokenMessageOptions[];
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: MessageTypes.GetAllResourceTokens;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
// -----------------------------
|
||||||
|
|
||||||
|
export type DataExploreMessageV2 =
|
||||||
|
| {
|
||||||
|
type: MessageTypes.Ready;
|
||||||
|
id: string;
|
||||||
|
params: [string]; // version
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: MessageTypes.GetAuthorizationToken;
|
||||||
|
id: string;
|
||||||
|
params: GetCosmosTokenMessageOptions[];
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: MessageTypes.GetAllResourceTokens;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetCosmosTokenMessageOptions = {
|
||||||
|
verb: "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace";
|
||||||
|
resourceType: "" | "dbs" | "colls" | "docs" | "sprocs" | "pkranges";
|
||||||
|
resourceId: string;
|
||||||
|
};
|
|
@ -1,6 +1,12 @@
|
||||||
import { AuthorizationToken, MessageTypes } from "./MessageTypes";
|
import { AuthorizationToken } from "./MessageTypes";
|
||||||
|
|
||||||
export type FabricMessage =
|
// This is the version of these messages
|
||||||
|
export const FABRIC_RPC_VERSION = "2";
|
||||||
|
|
||||||
|
// Fabric to Data Explorer
|
||||||
|
|
||||||
|
// TODO Deprecated. Remove this section once DE is updated
|
||||||
|
export type FabricMessageV1 =
|
||||||
| {
|
| {
|
||||||
type: "newContainer";
|
type: "newContainer";
|
||||||
databaseName: string;
|
databaseName: string;
|
||||||
|
@ -26,36 +32,50 @@ export type FabricMessage =
|
||||||
| {
|
| {
|
||||||
type: "allResourceTokens";
|
type: "allResourceTokens";
|
||||||
message: {
|
message: {
|
||||||
|
id: string;
|
||||||
|
error: string | undefined;
|
||||||
endpoint: string | undefined;
|
endpoint: string | undefined;
|
||||||
databaseId: string | undefined;
|
databaseId: string | undefined;
|
||||||
resourceTokens: unknown | undefined;
|
resourceTokens: unknown | undefined;
|
||||||
resourceTokensTimestamp: number | undefined;
|
resourceTokensTimestamp: number | undefined;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// -----------------------------
|
||||||
|
|
||||||
export type DataExploreMessage =
|
export type FabricMessageV2 =
|
||||||
| "ready"
|
|
||||||
| {
|
| {
|
||||||
type: MessageTypes.TelemetryInfo;
|
type: "newContainer";
|
||||||
data: {
|
databaseName: string;
|
||||||
action: "LoadDatabases";
|
|
||||||
actionModifier: "success" | "start";
|
|
||||||
defaultExperience: "SQL";
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: MessageTypes.GetAuthorizationToken;
|
type: "initialize";
|
||||||
|
version: string;
|
||||||
id: string;
|
id: string;
|
||||||
params: GetCosmosTokenMessageOptions[];
|
message: {
|
||||||
|
connectionId: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: MessageTypes.GetAllResourceTokens;
|
type: "authorizationToken";
|
||||||
|
message: {
|
||||||
|
id: string;
|
||||||
|
error: string | undefined;
|
||||||
|
data: AuthorizationToken | undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "allResourceTokens_v2";
|
||||||
|
message: {
|
||||||
|
id: string;
|
||||||
|
error: string | undefined;
|
||||||
|
data: FabricDatabaseConnectionInfo | undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "setToolbarStatus";
|
||||||
|
message: {
|
||||||
|
visible: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GetCosmosTokenMessageOptions = {
|
|
||||||
verb: "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace";
|
|
||||||
resourceType: "" | "dbs" | "colls" | "docs" | "sprocs" | "pkranges";
|
|
||||||
resourceId: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CosmosDBTokenResponse = {
|
export type CosmosDBTokenResponse = {
|
||||||
|
@ -66,12 +86,9 @@ export type CosmosDBTokenResponse = {
|
||||||
export type CosmosDBConnectionInfoResponse = {
|
export type CosmosDBConnectionInfoResponse = {
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
databaseId: string;
|
databaseId: string;
|
||||||
resourceTokens: unknown;
|
resourceTokens: { [resourceId: string]: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface FabricDatabaseConnectionInfo {
|
export interface FabricDatabaseConnectionInfo extends CosmosDBConnectionInfoResponse {
|
||||||
endpoint: string;
|
|
||||||
databaseId: string;
|
|
||||||
resourceTokens: { [resourceId: string]: string };
|
|
||||||
resourceTokensTimestamp: number;
|
resourceTokensTimestamp: number;
|
||||||
}
|
}
|
|
@ -1,6 +1,12 @@
|
||||||
/**
|
/**
|
||||||
* Messaging types used with Data Explorer <-> Portal communication,
|
* Messaging types used with Data Explorer <-> Portal communication,
|
||||||
* Hosted <-> Explorer communication and Data Explorer -> Fabric communication.
|
* Hosted <-> Explorer communication and Data Explorer -> Fabric communication.
|
||||||
|
*
|
||||||
|
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
* WARNING: !!!!!!! YOU CAN ONLY ADD NEW TYPES TO THE END OF THIS ENUM !!!!!!!
|
||||||
|
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
*
|
||||||
|
* Enum are integers, so inserting or deleting a type will break the communication.
|
||||||
*/
|
*/
|
||||||
export enum MessageTypes {
|
export enum MessageTypes {
|
||||||
TelemetryInfo,
|
TelemetryInfo,
|
||||||
|
@ -37,10 +43,9 @@ export enum MessageTypes {
|
||||||
DisplayNPSSurvey,
|
DisplayNPSSurvey,
|
||||||
OpenVCoreMongoNetworkingBlade,
|
OpenVCoreMongoNetworkingBlade,
|
||||||
OpenVCoreMongoConnectionStringsBlade,
|
OpenVCoreMongoConnectionStringsBlade,
|
||||||
|
GetAuthorizationToken, // Data Explorer -> Fabric
|
||||||
// Data Explorer -> Fabric communication
|
GetAllResourceTokens, // Data Explorer -> Fabric
|
||||||
GetAuthorizationToken,
|
Ready, // Data Explorer -> Fabric
|
||||||
GetAllResourceTokens,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthorizationToken {
|
export interface AuthorizationToken {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Platform, configContext } from "ConfigContext";
|
||||||
import { MessageTypes } from "Contracts/ExplorerContracts";
|
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||||
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||||
import { IGalleryItem } from "Juno/JunoClient";
|
import { IGalleryItem } from "Juno/JunoClient";
|
||||||
import { requestDatabaseResourceTokens } from "Platform/Fabric/FabricUtil";
|
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
|
||||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||||
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointValidation";
|
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointValidation";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
|
@ -384,9 +384,7 @@ export default class Explorer {
|
||||||
|
|
||||||
public onRefreshResourcesClick = (): void => {
|
public onRefreshResourcesClick = (): void => {
|
||||||
if (configContext.platform === Platform.Fabric) {
|
if (configContext.platform === Platform.Fabric) {
|
||||||
// Requesting the tokens will trigger a refresh of the databases
|
scheduleRefreshDatabaseResourceToken(true).then(() => this.refreshAllDatabases());
|
||||||
// TODO: Once the id is returned from Fabric, we can await this call and then refresh the databases here
|
|
||||||
requestDatabaseResourceTokens();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,16 +24,21 @@ interface Props {
|
||||||
export interface CommandBarStore {
|
export interface CommandBarStore {
|
||||||
contextButtons: CommandButtonComponentProps[];
|
contextButtons: CommandButtonComponentProps[];
|
||||||
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => void;
|
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => void;
|
||||||
|
isHidden: boolean;
|
||||||
|
setIsHidden: (isHidden: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCommandBar: UseStore<CommandBarStore> = create((set) => ({
|
export const useCommandBar: UseStore<CommandBarStore> = create((set) => ({
|
||||||
contextButtons: [],
|
contextButtons: [],
|
||||||
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })),
|
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })),
|
||||||
|
isHidden: false,
|
||||||
|
setIsHidden: (isHidden: boolean) => set((state) => ({ ...state, isHidden })),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
||||||
const selectedNodeState = useSelectedNode();
|
const selectedNodeState = useSelectedNode();
|
||||||
const buttons = useCommandBar((state) => state.contextButtons);
|
const buttons = useCommandBar((state) => state.contextButtons);
|
||||||
|
const isHidden = useCommandBar((state) => state.isHidden);
|
||||||
const backgroundColor = StyleConstants.BaseLight;
|
const backgroundColor = StyleConstants.BaseLight;
|
||||||
|
|
||||||
if (userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo") {
|
if (userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo") {
|
||||||
|
@ -42,7 +47,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
||||||
? CommandBarComponentButtonFactory.createPostgreButtons(container)
|
? CommandBarComponentButtonFactory.createPostgreButtons(container)
|
||||||
: CommandBarComponentButtonFactory.createVCoreMongoButtons(container);
|
: CommandBarComponentButtonFactory.createVCoreMongoButtons(container);
|
||||||
return (
|
return (
|
||||||
<div className="commandBarContainer">
|
<div className="commandBarContainer" style={{ display: isHidden ? "none" : "initial" }}>
|
||||||
<FluentCommandBar
|
<FluentCommandBar
|
||||||
ariaLabel="Use left and right arrow keys to navigate between commands"
|
ariaLabel="Use left and right arrow keys to navigate between commands"
|
||||||
items={CommandBarUtil.convertButton(buttons, backgroundColor)}
|
items={CommandBarUtil.convertButton(buttons, backgroundColor)}
|
||||||
|
@ -91,7 +96,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
||||||
? {
|
? {
|
||||||
root: {
|
root: {
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
padding: "0px 14px 0px 14px",
|
padding: "2px 8px 0px 8px",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
|
@ -101,7 +106,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="commandBarContainer">
|
<div className="commandBarContainer" style={{ display: isHidden ? "none" : "initial" }}>
|
||||||
<FluentCommandBar
|
<FluentCommandBar
|
||||||
ariaLabel="Use left and right arrow keys to navigate between commands"
|
ariaLabel="Use left and right arrow keys to navigate between commands"
|
||||||
items={uiFabricStaticButtons.concat(uiFabricTabsButtons)}
|
items={uiFabricStaticButtons.concat(uiFabricTabsButtons)}
|
||||||
|
|
|
@ -25,7 +25,10 @@ import { MemoryTracker } from "./MemoryTrackerComponent";
|
||||||
* @param btns
|
* @param btns
|
||||||
*/
|
*/
|
||||||
export const convertButton = (btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] => {
|
export const convertButton = (btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] => {
|
||||||
const buttonHeightPx = StyleConstants.CommandBarButtonHeight;
|
const buttonHeightPx =
|
||||||
|
configContext.platform == Platform.Fabric
|
||||||
|
? StyleConstants.FabricCommandBarButtonHeight
|
||||||
|
: StyleConstants.CommandBarButtonHeight;
|
||||||
|
|
||||||
const hoverColor =
|
const hoverColor =
|
||||||
configContext.platform == Platform.Fabric ? StyleConstants.FabricAccentLight : StyleConstants.AccentLight;
|
configContext.platform == Platform.Fabric ? StyleConstants.FabricAccentLight : StyleConstants.AccentLight;
|
||||||
|
@ -112,6 +115,7 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
|
||||||
splitButtonContainer: {
|
splitButtonContainer: {
|
||||||
marginLeft: 5,
|
marginLeft: 5,
|
||||||
marginRight: 5,
|
marginRight: 5,
|
||||||
|
height: buttonHeightPx,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
className: btn.className,
|
className: btn.className,
|
||||||
|
|
|
@ -1,33 +1,44 @@
|
||||||
import { sendCachedDataMessage } from "Common/MessageHandler";
|
import { sendCachedDataMessage } from "Common/MessageHandler";
|
||||||
import { FabricDatabaseConnectionInfo } from "Contracts/FabricContract";
|
import { FabricDatabaseConnectionInfo } from "Contracts/FabricMessagesContract";
|
||||||
import { MessageTypes } from "Contracts/MessageTypes";
|
import { MessageTypes } from "Contracts/MessageTypes";
|
||||||
import Explorer from "Explorer/Explorer";
|
import { updateUserContext, userContext } from "UserContext";
|
||||||
import { updateUserContext } from "UserContext";
|
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
||||||
|
|
||||||
const TOKEN_VALIDITY_MS = (3600 - 600) * 1000; // 1 hour minus 10 minutes to be safe
|
const TOKEN_VALIDITY_MS = (3600 - 600) * 1000; // 1 hour minus 10 minutes to be safe
|
||||||
|
const DEBOUNCE_DELAY_MS = 1000 * 20; // 20 second
|
||||||
let timeoutId: NodeJS.Timeout;
|
let timeoutId: NodeJS.Timeout;
|
||||||
|
|
||||||
// Prevents multiple parallel requests
|
// Prevents multiple parallel requests during DEBOUNCE_DELAY_MS
|
||||||
let isRequestPending = false;
|
let lastRequestTimestamp: number = undefined;
|
||||||
|
|
||||||
export const requestDatabaseResourceTokens = (): void => {
|
const requestDatabaseResourceTokens = async (): Promise<void> => {
|
||||||
if (isRequestPending) {
|
if (lastRequestTimestamp !== undefined && lastRequestTimestamp + DEBOUNCE_DELAY_MS > Date.now()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Make Fabric return the message id so we can handle this promise
|
lastRequestTimestamp = Date.now();
|
||||||
isRequestPending = true;
|
try {
|
||||||
sendCachedDataMessage<FabricDatabaseConnectionInfo>(MessageTypes.GetAllResourceTokens, []);
|
const fabricDatabaseConnectionInfo = await sendCachedDataMessage<FabricDatabaseConnectionInfo>(
|
||||||
};
|
MessageTypes.GetAllResourceTokens,
|
||||||
|
[],
|
||||||
|
userContext.fabricContext.connectionId,
|
||||||
|
);
|
||||||
|
|
||||||
export const handleRequestDatabaseResourceTokensResponse = (
|
if (!userContext.databaseAccount.properties.documentEndpoint) {
|
||||||
explorer: Explorer,
|
userContext.databaseAccount.properties.documentEndpoint = fabricDatabaseConnectionInfo.endpoint;
|
||||||
fabricDatabaseConnectionInfo: FabricDatabaseConnectionInfo,
|
}
|
||||||
): void => {
|
|
||||||
isRequestPending = false;
|
updateUserContext({
|
||||||
updateUserContext({ fabricDatabaseConnectionInfo });
|
fabricContext: { ...userContext.fabricContext, databaseConnectionInfo: fabricDatabaseConnectionInfo },
|
||||||
|
databaseAccount: { ...userContext.databaseAccount },
|
||||||
|
});
|
||||||
scheduleRefreshDatabaseResourceToken();
|
scheduleRefreshDatabaseResourceToken();
|
||||||
explorer.refreshAllDatabases();
|
} catch (error) {
|
||||||
|
logConsoleError(error);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
lastRequestTimestamp = undefined;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,19 +46,24 @@ export const handleRequestDatabaseResourceTokensResponse = (
|
||||||
* @param tokenTimestamp
|
* @param tokenTimestamp
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const scheduleRefreshDatabaseResourceToken = (): void => {
|
export const scheduleRefreshDatabaseResourceToken = (refreshNow?: boolean): Promise<void> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
if (timeoutId !== undefined) {
|
if (timeoutId !== undefined) {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
timeoutId = undefined;
|
timeoutId = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
timeoutId = setTimeout(() => {
|
timeoutId = setTimeout(
|
||||||
requestDatabaseResourceTokens();
|
() => {
|
||||||
}, TOKEN_VALIDITY_MS);
|
requestDatabaseResourceTokens().then(resolve);
|
||||||
|
},
|
||||||
|
refreshNow ? 0 : TOKEN_VALIDITY_MS,
|
||||||
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkDatabaseResourceTokensValidity = (tokenTimestamp: number): void => {
|
export const checkDatabaseResourceTokensValidity = (tokenTimestamp: number): void => {
|
||||||
if (tokenTimestamp + TOKEN_VALIDITY_MS < Date.now()) {
|
if (tokenTimestamp + TOKEN_VALIDITY_MS < Date.now()) {
|
||||||
requestDatabaseResourceTokens();
|
scheduleRefreshDatabaseResourceToken(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { FabricDatabaseConnectionInfo } from "Contracts/FabricContract";
|
import { FabricDatabaseConnectionInfo } 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";
|
||||||
|
@ -47,8 +47,13 @@ export interface VCoreMongoConnectionParams {
|
||||||
connectionString: string;
|
connectionString: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface FabricContext {
|
||||||
|
connectionId: string;
|
||||||
|
databaseConnectionInfo: FabricDatabaseConnectionInfo | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
interface UserContext {
|
interface UserContext {
|
||||||
readonly fabricDatabaseConnectionInfo?: FabricDatabaseConnectionInfo;
|
readonly fabricContext?: FabricContext;
|
||||||
readonly authType?: AuthType;
|
readonly authType?: AuthType;
|
||||||
readonly masterKey?: string;
|
readonly masterKey?: string;
|
||||||
readonly subscriptionId?: string;
|
readonly subscriptionId?: string;
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import { createUri } from "Common/UrlUtility";
|
import { createUri } from "Common/UrlUtility";
|
||||||
import { FabricDatabaseConnectionInfo, FabricMessage } from "Contracts/FabricContract";
|
import { DATA_EXPLORER_RPC_VERSION } from "Contracts/DataExplorerMessagesContract";
|
||||||
|
import { FABRIC_RPC_VERSION, FabricMessageV2 } from "Contracts/FabricMessagesContract";
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
|
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
import {
|
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
|
||||||
handleRequestDatabaseResourceTokensResponse,
|
|
||||||
scheduleRefreshDatabaseResourceToken,
|
|
||||||
} from "Platform/Fabric/FabricUtil";
|
|
||||||
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||||
|
@ -88,6 +87,9 @@ 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.
|
||||||
|
const SUPPORTED_FABRIC_VERSIONS = [FABRIC_RPC_VERSION];
|
||||||
|
|
||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
return new Promise<Explorer>((resolve) => {
|
return new Promise<Explorer>((resolve) => {
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
|
@ -101,38 +103,37 @@ async function configureFabric(): Promise<Explorer> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: FabricMessage = event.data?.data;
|
const data: FabricMessageV2 = event.data?.data;
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case "initialize": {
|
case "initialize": {
|
||||||
const fabricDatabaseConnectionInfo: FabricDatabaseConnectionInfo = {
|
const fabricVersion = data.version;
|
||||||
endpoint: data.message.endpoint,
|
if (!SUPPORTED_FABRIC_VERSIONS.includes(fabricVersion)) {
|
||||||
databaseId: data.message.databaseId,
|
// TODO Surface error to user
|
||||||
resourceTokens: data.message.resourceTokens as { [resourceId: string]: string },
|
console.error(`Unsupported Fabric version: ${fabricVersion}`);
|
||||||
resourceTokensTimestamp: data.message.resourceTokensTimestamp,
|
return;
|
||||||
};
|
}
|
||||||
explorer = await createExplorerFabric(fabricDatabaseConnectionInfo);
|
|
||||||
resolve(explorer);
|
|
||||||
|
|
||||||
explorer.refreshAllDatabases().then(() => {
|
explorer = createExplorerFabric(data.message);
|
||||||
openFirstContainer(explorer, fabricDatabaseConnectionInfo.databaseId);
|
await scheduleRefreshDatabaseResourceToken(true);
|
||||||
});
|
resolve(explorer);
|
||||||
scheduleRefreshDatabaseResourceToken();
|
await explorer.refreshAllDatabases();
|
||||||
|
openFirstContainer(explorer, userContext.fabricContext.databaseConnectionInfo.databaseId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "newContainer":
|
case "newContainer":
|
||||||
explorer.onNewCollectionClicked();
|
explorer.onNewCollectionClicked();
|
||||||
break;
|
break;
|
||||||
case "authorizationToken": {
|
case "authorizationToken":
|
||||||
|
case "allResourceTokens_v2": {
|
||||||
handleCachedDataMessage(data);
|
handleCachedDataMessage(data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "allResourceTokens": {
|
case "setToolbarStatus": {
|
||||||
// TODO call handleCachedDataMessage when Fabric echoes message id back
|
useCommandBar.getState().setIsHidden(data.message.visible === false);
|
||||||
handleRequestDatabaseResourceTokensResponse(explorer, data.message as FabricDatabaseConnectionInfo);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -143,7 +144,11 @@ async function configureFabric(): Promise<Explorer> {
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
sendReadyMessage();
|
sendMessage({
|
||||||
|
type: MessageTypes.Ready,
|
||||||
|
id: "ready",
|
||||||
|
params: [DATA_EXPLORER_RPC_VERSION],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,9 +324,12 @@ function configureHostedWithResourceToken(config: ResourceToken): Explorer {
|
||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createExplorerFabric(fabricDatabaseConnectionInfo: FabricDatabaseConnectionInfo): Explorer {
|
function createExplorerFabric(params: { connectionId: string }): Explorer {
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
fabricDatabaseConnectionInfo,
|
fabricContext: {
|
||||||
|
connectionId: params.connectionId,
|
||||||
|
databaseConnectionInfo: undefined,
|
||||||
|
},
|
||||||
authType: AuthType.ConnectionString,
|
authType: AuthType.ConnectionString,
|
||||||
databaseAccount: {
|
databaseAccount: {
|
||||||
id: "",
|
id: "",
|
||||||
|
@ -330,7 +338,7 @@ function createExplorerFabric(fabricDatabaseConnectionInfo: FabricDatabaseConnec
|
||||||
name: "Mounted",
|
name: "Mounted",
|
||||||
kind: AccountKind.Default,
|
kind: AccountKind.Default,
|
||||||
properties: {
|
properties: {
|
||||||
documentEndpoint: fabricDatabaseConnectionInfo.endpoint,
|
documentEndpoint: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue