Compare commits

...

14 Commits

Author SHA1 Message Date
Ashley Stanton-Nurse
08d3318a87 remove QueryEditor component 2024-03-29 11:47:45 -07:00
Ashley Stanton-Nurse
8b6d857ddb focus on the Copilotv2 editor 2024-03-28 15:26:13 -07:00
Ashley Stanton-Nurse
2598760a11 tinker with an "Execute Query" action in Monaco 2024-03-28 13:08:15 -07:00
Ashley Stanton-Nurse
44d886b4a0 restore some Monaco styles clobbered by our global styles 2024-03-28 10:26:52 -07:00
sunghyunkang1111
5aa6b0abe1 fix opening collections (#1780)
* fix opening collections

* fix opening collections

* fix opening collections

* fix opening collections
2024-03-28 12:10:32 -05:00
Vsevolod Kukol
f24b0bcf1b Show the Feedback button in Portal only (#1775)
This feature is not supported on any other platform incl. Hosted.
2024-03-28 16:05:38 +01:00
JustinKol
56408a97d7 Add container ids to tabs (#1772)
* Added container ids to tabs

* prettier run

* Updated for undefined scenarios

* prettier

* added ellipsis to long container names

* added slice

* prettier

* Added ellipsis to long DB names in tabs

* Added undefined DB case

* Replaced dots with ellipsis character

* corrected undefined return value
2024-03-26 12:36:04 -04:00
Vsevolod Kukol
0df68c4967 Fix initial container loading in Fabric (#1771)
* Fix initial container loading in Fabric

There is a rendering issue where the documents table doesn't resize properly if explorer is loaded in the beackground (invisible).
To workaround this, track DE visibility from Fabric RPC and open the first container only once DE becomes visible. If DE has been already shown, open the container right away.

* Preserve glitchy behavior if Fabric UX doesn't send isVisible

* Preserve Fabric visibility in global status
and fix a race condition where visibility might change during initialization.
2024-03-26 17:22:15 +01:00
Asier Isayas
e09930d9d0 Support Token API in new Portal Backend (#1773)
* added support for generate token

* fix checks

* fix checks

* deactivate mongo proxy

* fix tests

* remove mongo proxy from mpac

* change endpoints to prod

* npm run format

* add await

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-03-22 13:18:02 -04:00
sunghyunkang1111
da2e874ae6 Fix bugs on data transfer and bring back query explanation and remove query prompt from editor (#1777)
* Fix minor issues

* add back preview tag

* bring back query explanation and remove prompt in editor
2024-03-21 11:23:42 -05:00
Vsevolod Kukol
a524138ac9 Don't show the new Home button in Fabric (#1774)
as the whole Home tab feature is not supported there.
2024-03-20 00:28:24 +01:00
sindhuba
39b0fb9e2c Fix API endpoint for CassandraProxy query API (#1769) 2024-03-18 10:49:33 -07:00
sunghyunkang1111
ac22e88d9c rebranding of inline copilot (#1767)
* rebranding of inline copilot

* rebranding of inline copilot

* rebranding of inline copilot

* fix styling
2024-03-18 12:15:24 -05:00
Laurent Nguyen
91d9e27049 Turn off fetching authorization token (#1766) 2024-03-14 21:56:26 +01:00
38 changed files with 902 additions and 479 deletions

View File

@@ -2296,6 +2296,17 @@ a:link {
display: none !important;
}
.monaco-editor .quick-input-list-label {
/* Restore some of Monaco's default styles that are clobbered by our global styles */
padding: 0;
line-height: 22px;
}
.monaco-editor .quick-input-list .highlight {
/* Padding in highlighted text within the quick input list breaks the flow of the text */
padding: 0;
}
td a {
color: #393939;
}

View File

@@ -124,6 +124,34 @@ export enum MongoBackendEndpointType {
remote,
}
export enum BackendApi {
GenerateToken,
}
export class PortalBackendEndpoints {
public static readonly Development: string = "https://localhost:7235";
public static readonly Mpac: string = "https://cdb-ms-mpac-pbe.cosmos.azure.com";
public static readonly Prod: string = "https://cdb-ms-prod-pbe.cosmos.azure.com";
public static readonly Fairfax: string = "https://cdb-ff-prod-pbe.cosmos.azure.us";
public static readonly Mooncake: string = "https://cdb-mc-prod-pbe.cosmos.azure.cn";
}
export class MongoProxyEndpoints {
public static readonly Development: string = "https://localhost:7238";
public static readonly Mpac: string = "https://cdb-ms-mpac-mp.cosmos.azure.com";
public static readonly Prod: string = "https://cdb-ms-prod-mp.cosmos.azure.com";
public static readonly Fairfax: string = "https://cdb-ff-prod-mp.cosmos.azure.us";
public static readonly Mooncake: string = "https://cdb-mc-prod-mp.cosmos.azure.cn";
}
export class CassandraProxyEndpoints {
public static readonly Development: string = "https://localhost:7240";
public static readonly Mpac: string = "https://cdb-ms-mpac-cp.cosmos.azure.com";
public static readonly Prod: string = "https://cdb-ms-prod-cp.cosmos.azure.com";
public static readonly Fairfax: string = "https://cdb-ff-prod-cp.cosmos.azure.us";
public static readonly Mooncake: string = "https://cdb-mc-prod-cp.cosmos.azure.cn";
}
//TODO: Remove this when new backend is migrated over
export class CassandraBackend {
public static readonly createOrDeleteApi: string = "api/cassandra/createordelete";
@@ -139,7 +167,7 @@ export class CassandraBackend {
export class CassandraProxyAPIs {
public static readonly createOrDeleteApi: string = "api/cassandra/createordelete";
public static readonly connectionStringCreateOrDeleteApi: string = "api/connectionstring/cassandra/createordelete";
public static readonly queryApi: string = "api/cassandra/postquery";
public static readonly queryApi: string = "api/cassandra";
public static readonly connectionStringQueryApi: string = "api/connectionstring/cassandra";
public static readonly keysApi: string = "api/cassandra/keys";
public static readonly connectionStringKeysApi: string = "api/connectionstring/cassandra/keys";
@@ -446,22 +474,6 @@ export class JunoEndpoints {
public static readonly Stage = "https://tools-staging.cosmos.azure.com";
}
export class MongoProxyEndpoints {
public static readonly Development: string = "https://localhost:7238";
public static readonly Mpac: string = "https://cdb-ms-mpac-mp.cosmos.azure.com";
public static readonly Prod: string = "https://cdb-ms-prod-mp.cosmos.azure.com";
public static readonly Fairfax: string = "https://cdb-ff-prod-mp.cosmos.azure.us";
public static readonly Mooncake: string = "https://cdb-mc-prod-mp.cosmos.azure.cn";
}
export class CassandraProxyEndpoints {
public static readonly Development: string = "https://localhost:7240";
public static readonly Mpac: string = "https://cdb-ms-mpac-cp.cosmos.azure.com";
public static readonly Prod: string = "https://cdb-ms-prod-cp.cosmos.azure.com";
public static readonly Fairfax: string = "https://cdb-ff-prod-cp.cosmos.azure.us";
public static readonly Mooncake: string = "https://cdb-mc-prod-cp.cosmos.azure.cn";
}
export class PriorityLevel {
public static readonly High = "high";
public static readonly Low = "low";

View File

@@ -1,7 +1,6 @@
import * as Cosmos from "@azure/cosmos";
import { sendCachedDataMessage } from "Common/MessageHandler";
import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizationTokenUsingResourceTokens";
import { AuthorizationToken, MessageTypes } from "Contracts/MessageTypes";
import { AuthorizationToken } from "Contracts/MessageTypes";
import { checkDatabaseResourceTokensValidity } from "Platform/Fabric/FabricUtil";
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { AuthType } from "../AuthType";
@@ -51,15 +50,23 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
case Cosmos.ResourceType.offer:
case Cosmos.ResourceType.user:
case Cosmos.ResourceType.permission:
// User master tokens
const authorizationToken = await sendCachedDataMessage<AuthorizationToken>(
MessageTypes.GetAuthorizationToken,
[requestInfo],
userContext.fabricContext.connectionId,
);
console.log("Response from Fabric: ", authorizationToken);
headers[HttpHeaders.msDate] = authorizationToken.XDate;
return decodeURIComponent(authorizationToken.PrimaryReadWriteToken);
// 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
// (which is a valid token, but won't work for these operations).
const resourceTokens2 = userContext.fabricContext.databaseConnectionInfo.resourceTokens;
return getAuthorizationTokenUsingResourceTokens(resourceTokens2, requestInfo.path, requestInfo.resourceId);
/* ************** TODO: Uncomment this code if we need to support these operations **************
// User master tokens
const authorizationToken = await sendCachedDataMessage<AuthorizationToken>(
MessageTypes.GetAuthorizationToken,
[requestInfo],
userContext.fabricContext.connectionId,
);
console.log("Response from Fabric: ", authorizationToken);
headers[HttpHeaders.msDate] = authorizationToken.XDate;
return decodeURIComponent(authorizationToken.PrimaryReadWriteToken);
***********************************************************************************************/
}
}

View File

@@ -0,0 +1,25 @@
import { KeyMap } from "react-hotkeys";
export const keyMap: KeyMap = {
NEW_QUERY: {
name: "New Query",
sequence: "ctrl+j",
action: "keydown",
},
CANCEL_QUERY: {
name: "Cancel Query",
sequence: "f8",
action: "keydown",
},
DISCARD: {
name: "Discard Changes",
sequence: "ctrl+x",
action: "keydown"
}
};
export type KeyboardShortcutName = keyof typeof keyMap;
export type KeyboardShortcutHandlers = Partial<{
[key in KeyboardShortcutName]: (keyEvent?: KeyboardEvent) => void;
}>;

View File

@@ -690,6 +690,7 @@ export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameter
}
function useMongoProxyEndpoint(api: string): boolean {
const activeMongoProxyEndpoints: string[] = [MongoProxyEndpoints.Development];
let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
if (userContext.databaseAccount.properties.ipRules?.length > 0) {
canAccessMongoProxy = canAccessMongoProxy && configContext.MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED;
@@ -698,6 +699,6 @@ function useMongoProxyEndpoint(api: string): boolean {
return (
canAccessMongoProxy &&
configContext.NEW_MONGO_APIS?.includes(api) &&
[MongoProxyEndpoints.Development, MongoProxyEndpoints.Mpac].includes(configContext.MONGO_PROXY_ENDPOINT)
activeMongoProxyEndpoints.includes(configContext.MONGO_PROXY_ENDPOINT)
);
}

View File

@@ -122,14 +122,21 @@ const pollDataTransferJobOperation = async (
updateDataTransferJob(body);
if (status === "Cancelled" || status === "Failed" || status === "Faulted") {
if (status === "Cancelled") {
removeFromPolling(jobName);
clearMessage && clearMessage();
const cancelMessage = `Data transfer job ${jobName} cancelled`;
NotificationConsoleUtils.logConsoleError(cancelMessage);
throw new AbortError(cancelMessage);
}
if (status === "Failed" || status === "Faulted") {
removeFromPolling(jobName);
const errorMessage = body?.properties?.error
? JSON.stringify(body?.properties?.error)
: "Operation could not be completed";
const error = new Error(errorMessage);
clearMessage && clearMessage();
NotificationConsoleUtils.logConsoleError(`Data transfer job ${jobName} Failed`);
NotificationConsoleUtils.logConsoleError(`Data transfer job ${jobName} failed: ${errorMessage}`);
throw new AbortError(error);
}
if (status === "Completed") {

View File

@@ -1,4 +1,10 @@
import { CassandraProxyEndpoints, JunoEndpoints, MongoProxyEndpoints } from "Common/Constants";
import {
BackendApi,
CassandraProxyEndpoints,
JunoEndpoints,
MongoProxyEndpoints,
PortalBackendEndpoints,
} from "Common/Constants";
import {
allowedAadEndpoints,
allowedArcadiaEndpoints,
@@ -39,6 +45,8 @@ export interface ConfigContext {
ARCADIA_ENDPOINT: string;
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
BACKEND_ENDPOINT?: string;
PORTAL_BACKEND_ENDPOINT?: string;
NEW_BACKEND_APIS?: BackendApi[];
MONGO_BACKEND_ENDPOINT?: string;
MONGO_PROXY_ENDPOINT?: string;
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED?: boolean;
@@ -90,6 +98,8 @@ let configContext: Readonly<ConfigContext> = {
GITHUB_TEST_ENV_CLIENT_ID: "b63fc8cbf87fd3c6e2eb", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1777772
JUNO_ENDPOINT: JunoEndpoints.Prod,
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod,
NEW_BACKEND_APIS: [BackendApi.GenerateToken],
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
NEW_MONGO_APIS: [
// "resourcelist",

View File

@@ -53,6 +53,7 @@ export type FabricMessageV2 =
id: string;
message: {
connectionId: string;
isVisible: boolean;
};
}
| {
@@ -72,7 +73,7 @@ export type FabricMessageV2 =
};
}
| {
type: "setToolbarStatus";
type: "explorerVisible";
message: {
visible: boolean;
};

View File

@@ -1,6 +1,7 @@
/**
* React component for Command button component.
*/
import { KeyboardShortcutName } from "Common/KeyboardShortcuts";
import * as React from "react";
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
import { KeyCodes } from "../../../Common/Constants";
@@ -30,7 +31,7 @@ export interface CommandButtonComponentProps {
/**
* Click handler for command button click
*/
onCommandClick: (e: React.SyntheticEvent) => void;
onCommandClick: (e: React.SyntheticEvent | KeyboardEvent) => void;
/**
* Label for the button
@@ -107,10 +108,16 @@ export interface CommandButtonComponentProps {
* Vertical bar to divide buttons
*/
isDivider?: boolean;
/**
* Aria-label for the button
*/
ariaLabel: string;
/**
* A keyboard shortcut that can be used to activate this button.
*/
keyboardShortcut?: KeyboardShortcutName;
}
export class CommandButtonComponent extends React.Component<CommandButtonComponentProps> {

View File

@@ -1,6 +1,6 @@
import { Spinner, SpinnerSize } from "@fluentui/react";
import * as React from "react";
import { loadMonaco, monaco } from "../../LazyMonaco";
import { MonacoNamespace, loadMonaco, monaco } from "../../LazyMonaco";
// import "./EditorReact.less";
interface EditorReactStates {
@@ -21,6 +21,7 @@ export interface EditorReactProps {
minimap?: monaco.editor.IEditorOptions["minimap"];
scrollBeyondLastLine?: monaco.editor.IEditorOptions["scrollBeyondLastLine"];
monacoContainerStyles?: React.CSSProperties;
configureEditor?: (monaco: MonacoNamespace, editor: monaco.editor.IStandaloneCodeEditor) => void;
}
export class EditorReact extends React.Component<EditorReactProps, EditorReactStates> {
@@ -69,7 +70,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
);
}
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
protected configureEditor(monaco: MonacoNamespace, editor: monaco.editor.IStandaloneCodeEditor) {
this.editor = editor;
const queryEditorModel = this.editor.getModel();
if (!this.props.isReadOnly && this.props.onContentChanged) {
@@ -87,12 +88,16 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
},
);
}
if (this.props.configureEditor) {
this.props.configureEditor(monaco, this.editor);
}
}
/**
* Create the monaco editor and attach to DOM
*/
private async createEditor(createCallback: (e: monaco.editor.IStandaloneCodeEditor) => void) {
private async createEditor(createCallback: (monaco: MonacoNamespace, e: monaco.editor.IStandaloneCodeEditor) => void) {
const options: monaco.editor.IStandaloneEditorConstructionOptions = {
language: this.props.language,
value: this.props.content,
@@ -111,7 +116,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
this.rootNode.innerHTML = "";
const monaco = await loadMonaco();
createCallback(monaco?.editor?.create(this.rootNode, options));
createCallback(monaco, monaco?.editor?.create(this.rootNode, options));
if (this.rootNode.innerHTML) {
this.setState({

View File

@@ -754,6 +754,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
buttons.push({
iconSrc: DiscardIcon,
iconAlt: label,
keyboardShortcut: "DISCARD",
onCommandClick: this.onRevertClick,
commandButtonLabel: label,
ariaLabel: label,

View File

@@ -136,15 +136,15 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({ da
};
const getPercentageComplete = () => {
const jobStatus = portalDataTransferJob?.properties?.status;
const isCompleted = jobStatus === "Completed";
if (isCompleted) {
return 1;
}
const processedCount = portalDataTransferJob?.properties?.processedCount;
const totalCount = portalDataTransferJob?.properties?.totalCount;
const jobStatus = portalDataTransferJob?.properties?.status;
const isCancelled = jobStatus === "Cancelled";
const isCompleted = jobStatus === "Completed";
if (totalCount <= 0 && !isCompleted) {
return isCancelled ? 0 : null;
}
return isCompleted ? 1 : processedCount / totalCount;
const isJobInProgress = isCurrentJobInProgress(portalDataTransferJob);
return isJobInProgress ? (totalCount > 0 ? processedCount / totalCount : null) : 0;
};
return (

View File

@@ -149,7 +149,7 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => {
case SettingsV2TabTypes.IndexingPolicyTab:
return "Indexing Policy";
case SettingsV2TabTypes.PartitionKeyTab:
return "Partition Keys";
return "Partition Keys (preview)";
case SettingsV2TabTypes.ComputedPropertiesTab:
return "Computed Properties (preview)";
default:

View File

@@ -207,7 +207,7 @@ exports[`SettingsComponent renders 1`] = `
/>
</PivotItem>
<PivotItem
headerText="Partition Keys"
headerText="Partition Keys (preview)"
itemKey="PartitionKeyTab"
key="PartitionKeyTab"
style={

View File

@@ -3,3 +3,4 @@ export type { monaco };
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const loadMonaco = () => import(/* webpackChunkName: "lazy-monaco" */ "monaco-editor/esm/vs/editor/editor.api");
export type MonacoNamespace = Awaited<ReturnType<typeof loadMonaco>>;

View File

@@ -4,9 +4,11 @@
* and update any knockout observables passed from the parent.
*/
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
import { keyMap } from "Common/KeyboardShortcuts";
import { useNotebook } from "Explorer/Notebook/useNotebook";
import { userContext } from "UserContext";
import * as React from "react";
import { GlobalHotKeys } from "react-hotkeys";
import create, { UseStore } from "zustand";
import { ConnectionStatusType, PoolIdType } from "../../../Common/Constants";
import { StyleConstants } from "../../../Common/StyleConstants";
@@ -105,8 +107,13 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
},
};
const handlers = CommandBarUtil.createKeyboardHandlers(staticButtons.concat(contextButtons).concat(controlButtons));
return (
<div className="commandBarContainer" style={{ display: isHidden ? "none" : "initial" }}>
{/* Handles keyboard shortcuts for command bar buttons when focus is OUTSIDE monaco. Even though it's placed here in the DOM, it hooks keydown on 'document' */}
<GlobalHotKeys keyMap={keyMap} handlers={handlers} allowChanges={true} />
<FluentCommandBar
ariaLabel="Use left and right arrow keys to navigate between commands"
items={uiFabricStaticButtons.concat(uiFabricTabsButtons)}

View File

@@ -58,10 +58,10 @@ export function createStaticCommandBarButtons(
}
};
const homeBtn = createHomeButton();
buttons.push(homeBtn);
if (configContext.platform !== Platform.Fabric) {
const homeBtn = createHomeButton();
buttons.push(homeBtn);
const newCollectionBtn = createNewCollectionGroup(container);
buttons.push(newCollectionBtn);
if (userContext.apiType !== "Tables" && userContext.apiType !== "Cassandra") {
@@ -240,7 +240,7 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
buttons.push(fullScreenButton);
}
if (configContext.platform !== Platform.Emulator) {
if (configContext.platform === Platform.Portal) {
const label = "Feedback";
const feedbackButtonOptions: CommandButtonComponentProps = {
iconSrc: FeedbackIcon,
@@ -354,6 +354,7 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB
id: "newQueryBtn",
iconSrc: AddSqlQueryIcon,
iconAlt: label,
keyboardShortcut: "NEW_QUERY",
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection);
@@ -369,6 +370,7 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB
id: "newQueryBtn",
iconSrc: AddSqlQueryIcon,
iconAlt: label,
keyboardShortcut: "NEW_QUERY",
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection);

View File

@@ -6,6 +6,7 @@ import {
IDropdownOption,
IDropdownStyles,
} from "@fluentui/react";
import { KeyboardShortcutHandlers } from "Common/KeyboardShortcuts";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import * as React from "react";
import _ from "underscore";
@@ -233,3 +234,16 @@ export const createConnectionStatus = (container: Explorer, poolId: PoolIdType,
onRender: () => <ConnectionStatus container={container} poolId={poolId} />,
};
};
export const createKeyboardHandlers = (buttons: CommandButtonComponentProps[]): KeyboardShortcutHandlers => {
const handlers: KeyboardShortcutHandlers = {};
buttons.forEach((button) => {
if (button.keyboardShortcut) {
handlers[button.keyboardShortcut] = (e) => {
button.onCommandClick(e);
e.preventDefault();
};
}
});
return handlers;
}

View File

@@ -1,4 +1,5 @@
// TODO convert this file to an action registry in order to have actions and their handlers be more tightly coupled.
import { useDatabases } from "Explorer/useDatabases";
import React from "react";
import { ActionContracts } from "../../Contracts/ExplorerContracts";
import * as ViewModels from "../../Contracts/ViewModels";
@@ -40,97 +41,112 @@ function openCollectionTab(
databases: ViewModels.Database[],
initialDatabaseIndex = 0,
) {
for (let i = initialDatabaseIndex; i < databases.length; i++) {
const database: ViewModels.Database = databases[i];
if (!!action.databaseResourceId && database.id() !== action.databaseResourceId) {
continue;
}
const collectionActionHandler = (collections: ViewModels.Collection[]) => {
if (!action.collectionResourceId && collections.length === 0) {
subscription.dispose();
openCollectionTab(action, databases, ++i);
return;
}
for (let j = 0; j < collections.length; j++) {
const collection: ViewModels.Collection = collections[j];
if (!!action.collectionResourceId && collection.id() !== action.collectionResourceId) {
continue;
}
// select the collection
collection.expandCollection();
if (
action.tabKind === ActionContracts.TabKind.SQLDocuments ||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLDocuments]
) {
collection.onDocumentDBDocumentsClick();
break;
}
if (
action.tabKind === ActionContracts.TabKind.MongoDocuments ||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.MongoDocuments]
) {
collection.onMongoDBDocumentsClick();
break;
}
if (
action.tabKind === ActionContracts.TabKind.SchemaAnalyzer ||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SchemaAnalyzer]
) {
collection.onSchemaAnalyzerClick();
break;
}
if (
action.tabKind === ActionContracts.TabKind.TableEntities ||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.TableEntities]
) {
collection.onTableEntitiesClick();
break;
}
if (
action.tabKind === ActionContracts.TabKind.Graph ||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.Graph]
) {
collection.onGraphDocumentsClick();
break;
}
if (
action.tabKind === ActionContracts.TabKind.SQLQuery ||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLQuery]
) {
collection.onNewQueryClick(
collection,
undefined,
generateQueryText(action as ActionContracts.OpenQueryTab, collection.partitionKeyProperties),
);
break;
}
if (
action.tabKind === ActionContracts.TabKind.ScaleSettings ||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.ScaleSettings]
) {
collection.onSettingsClick();
break;
}
}
subscription.dispose();
//if databases are not yet loaded, wait until loaded
if (!databases || databases.length === 0) {
const databaseActionHandler = (databases: ViewModels.Database[]) => {
databasesUnsubscription();
openCollectionTab(action, databases, 0);
return;
};
const databasesUnsubscription = useDatabases.subscribe(databaseActionHandler, (state) => state.databases);
} else {
for (let i = initialDatabaseIndex; i < databases.length; i++) {
const database: ViewModels.Database = databases[i];
if (!!action.databaseResourceId && database.id() !== action.databaseResourceId) {
continue;
}
const subscription = database.collections.subscribe((collections) => collectionActionHandler(collections));
if (database.collections && database.collections() && database.collections().length) {
collectionActionHandler(database.collections());
//expand database first if not expanded to load the collections
if (!database.isDatabaseExpanded?.()) {
database.expandDatabase?.();
}
const collectionActionHandler = (collections: ViewModels.Collection[]) => {
if (!action.collectionResourceId && collections.length === 0) {
subscription.dispose();
openCollectionTab(action, databases, ++i);
return;
}
for (let j = 0; j < collections.length; j++) {
const collection: ViewModels.Collection = collections[j];
if (!!action.collectionResourceId && collection.id() !== action.collectionResourceId) {
continue;
}
// select the collection
collection.expandCollection();
if (
action.tabKind === ActionContracts.TabKind.SQLDocuments ||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLDocuments]
) {
collection.onDocumentDBDocumentsClick();
break;
}
if (
action.tabKind === ActionContracts.TabKind.MongoDocuments ||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.MongoDocuments]
) {
collection.onMongoDBDocumentsClick();
break;
}
if (
action.tabKind === ActionContracts.TabKind.SchemaAnalyzer ||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SchemaAnalyzer]
) {
collection.onSchemaAnalyzerClick();
break;
}
if (
action.tabKind === ActionContracts.TabKind.TableEntities ||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.TableEntities]
) {
collection.onTableEntitiesClick();
break;
}
if (
action.tabKind === ActionContracts.TabKind.Graph ||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.Graph]
) {
collection.onGraphDocumentsClick();
break;
}
if (
action.tabKind === ActionContracts.TabKind.SQLQuery ||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLQuery]
) {
collection.onNewQueryClick(
collection,
undefined,
generateQueryText(action as ActionContracts.OpenQueryTab, collection.partitionKeyProperties),
);
break;
}
if (
action.tabKind === ActionContracts.TabKind.ScaleSettings ||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.ScaleSettings]
) {
collection.onSettingsClick();
break;
}
}
subscription.dispose();
};
const subscription = database.collections.subscribe((collections) => collectionActionHandler(collections));
if (database.collections && database.collections() && database.collections().length) {
collectionActionHandler(database.collections());
}
break;
}
break;
}
}

View File

@@ -6,6 +6,7 @@ import {
Icon,
IconButton,
Link,
MessageBar,
Stack,
Text,
TooltipHost,
@@ -207,6 +208,7 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
</Stack>
{createNewContainer ? (
<Stack>
<MessageBar>All configurations except for unique keys will be copied from the source container</MessageBar>
<Stack className="panelGroupSpacing">
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>

View File

@@ -49,7 +49,7 @@ export const QueryCopilotFeedbackModal = ({
};
return (
<Modal isOpen={showFeedbackModal}>
<Modal isOpen={showFeedbackModal} styles={{ main: { borderRadius: 8, maxWidth: 600 } }}>
<form onSubmit={handleSubmit}>
<Stack style={{ padding: 24 }}>
<Stack horizontal horizontalAlign="space-between">
@@ -68,9 +68,14 @@ export const QueryCopilotFeedbackModal = ({
rows={3}
/>
<TextField
styles={{ root: { marginBottom: 14 } }}
styles={{
root: { marginBottom: 14 },
fieldGroup: { backgroundColor: "#F3F2F1", borderRadius: 4, borderColor: "#D1D1D1" },
}}
label="Query generated"
defaultValue={generatedQuery}
multiline
rows={3}
readOnly
/>
<Text style={{ fontSize: 12, marginBottom: 14 }}>

View File

@@ -3,6 +3,14 @@
exports[`Query Copilot Feedback Modal snapshot test shoud render and match snapshot 1`] = `
<Modal
isOpen={true}
styles={
Object {
"main": Object {
"borderRadius": 8,
"maxWidth": 600,
},
}
}
>
<form
onSubmit={[Function]}
@@ -67,9 +75,16 @@ exports[`Query Copilot Feedback Modal snapshot test shoud render and match snaps
<StyledTextFieldBase
defaultValue="test query"
label="Query generated"
multiline={true}
readOnly={true}
rows={3}
styles={
Object {
"fieldGroup": Object {
"backgroundColor": "#F3F2F1",
"borderColor": "#D1D1D1",
"borderRadius": 4,
},
"root": Object {
"marginBottom": 14,
},
@@ -125,6 +140,14 @@ exports[`Query Copilot Feedback Modal snapshot test shoud render and match snaps
exports[`Query Copilot Feedback Modal snapshot test should cancel submission 1`] = `
<Modal
isOpen={false}
styles={
Object {
"main": Object {
"borderRadius": 8,
"maxWidth": 600,
},
}
}
>
<form
onSubmit={[Function]}
@@ -189,9 +212,16 @@ exports[`Query Copilot Feedback Modal snapshot test should cancel submission 1`]
<StyledTextFieldBase
defaultValue="test query"
label="Query generated"
multiline={true}
readOnly={true}
rows={3}
styles={
Object {
"fieldGroup": Object {
"backgroundColor": "#F3F2F1",
"borderColor": "#D1D1D1",
"borderRadius": 4,
},
"root": Object {
"marginBottom": 14,
},
@@ -247,6 +277,14 @@ exports[`Query Copilot Feedback Modal snapshot test should cancel submission 1`]
exports[`Query Copilot Feedback Modal snapshot test should close on cancel click 1`] = `
<Modal
isOpen={false}
styles={
Object {
"main": Object {
"borderRadius": 8,
"maxWidth": 600,
},
}
}
>
<form
onSubmit={[Function]}
@@ -311,9 +349,16 @@ exports[`Query Copilot Feedback Modal snapshot test should close on cancel click
<StyledTextFieldBase
defaultValue="test query"
label="Query generated"
multiline={true}
readOnly={true}
rows={3}
styles={
Object {
"fieldGroup": Object {
"backgroundColor": "#F3F2F1",
"borderColor": "#D1D1D1",
"borderRadius": 4,
},
"root": Object {
"marginBottom": 14,
},
@@ -369,6 +414,14 @@ exports[`Query Copilot Feedback Modal snapshot test should close on cancel click
exports[`Query Copilot Feedback Modal snapshot test should get user unput 1`] = `
<Modal
isOpen={false}
styles={
Object {
"main": Object {
"borderRadius": 8,
"maxWidth": 600,
},
}
}
>
<form
onSubmit={[Function]}
@@ -433,9 +486,16 @@ exports[`Query Copilot Feedback Modal snapshot test should get user unput 1`] =
<StyledTextFieldBase
defaultValue="test query"
label="Query generated"
multiline={true}
readOnly={true}
rows={3}
styles={
Object {
"fieldGroup": Object {
"backgroundColor": "#F3F2F1",
"borderColor": "#D1D1D1",
"borderRadius": 4,
},
"root": Object {
"marginBottom": 14,
},
@@ -491,6 +551,14 @@ exports[`Query Copilot Feedback Modal snapshot test should get user unput 1`] =
exports[`Query Copilot Feedback Modal snapshot test should not render dont show again button 1`] = `
<Modal
isOpen={false}
styles={
Object {
"main": Object {
"borderRadius": 8,
"maxWidth": 600,
},
}
}
>
<form
onSubmit={[Function]}
@@ -555,9 +623,16 @@ exports[`Query Copilot Feedback Modal snapshot test should not render dont show
<StyledTextFieldBase
defaultValue="test query"
label="Query generated"
multiline={true}
readOnly={true}
rows={3}
styles={
Object {
"fieldGroup": Object {
"backgroundColor": "#F3F2F1",
"borderColor": "#D1D1D1",
"borderRadius": 4,
},
"root": Object {
"marginBottom": 14,
},
@@ -613,6 +688,14 @@ exports[`Query Copilot Feedback Modal snapshot test should not render dont show
exports[`Query Copilot Feedback Modal snapshot test should render dont show again button and check it 1`] = `
<Modal
isOpen={true}
styles={
Object {
"main": Object {
"borderRadius": 8,
"maxWidth": 600,
},
}
}
>
<form
onSubmit={[Function]}
@@ -677,9 +760,16 @@ exports[`Query Copilot Feedback Modal snapshot test should render dont show agai
<StyledTextFieldBase
defaultValue="test query"
label="Query generated"
multiline={true}
readOnly={true}
rows={3}
styles={
Object {
"fieldGroup": Object {
"backgroundColor": "#F3F2F1",
"borderColor": "#D1D1D1",
"borderRadius": 4,
},
"root": Object {
"marginBottom": 14,
},
@@ -750,6 +840,14 @@ exports[`Query Copilot Feedback Modal snapshot test should render dont show agai
exports[`Query Copilot Feedback Modal snapshot test should submit submission 1`] = `
<Modal
isOpen={false}
styles={
Object {
"main": Object {
"borderRadius": 8,
"maxWidth": 600,
},
}
}
>
<form
onSubmit={[Function]}
@@ -814,9 +912,16 @@ exports[`Query Copilot Feedback Modal snapshot test should submit submission 1`]
<StyledTextFieldBase
defaultValue="test query"
label="Query generated"
multiline={true}
readOnly={true}
rows={3}
styles={
Object {
"fieldGroup": Object {
"backgroundColor": "#F3F2F1",
"borderColor": "#D1D1D1",
"borderRadius": 4,
},
"root": Object {
"marginBottom": 14,
},

View File

@@ -11,8 +11,8 @@ import {
Link,
MessageBar,
MessageBarType,
ProgressIndicator,
Separator,
Spinner,
Stack,
TeachingBubble,
Text,
@@ -36,7 +36,6 @@ import { userContext } from "UserContext";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import React, { useRef, useState } from "react";
import HintIcon from "../../../images/Hint.svg";
import CopilotIcon from "../../../images/QueryCopilotNewLogo.svg";
import RecentIcon from "../../../images/Recent.svg";
import errorIcon from "../../../images/close-black.svg";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
@@ -215,12 +214,12 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json();
if (response.ok) {
if (generateSQLQueryResponse?.sql !== "N/A") {
let query = `-- **Prompt:** ${userPrompt}\r\n`;
if (generateSQLQueryResponse.explanation) {
query += `-- **Explanation of query:** ${generateSQLQueryResponse.explanation}\r\n`;
}
query += generateSQLQueryResponse.sql;
setQuery(query);
const queryExplanation = `-- **Explanation of query:** ${
generateSQLQueryResponse.explanation ? generateSQLQueryResponse.explanation : "N/A"
}\r\n`;
const currentGeneratedQuery = queryExplanation + generateSQLQueryResponse.sql;
const lastQuery = generatedQuery && query ? `${query}\r\n` : "";
setQuery(`${lastQuery}${currentGeneratedQuery}`);
setGeneratedQuery(generateSQLQueryResponse.sql);
setGeneratedQueryComments(generateSQLQueryResponse.explanation);
setShowFeedbackBar(true);
@@ -310,12 +309,388 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
return (
<Stack
className="copilot-prompt-pane"
styles={{ root: { backgroundColor: "#FAFAFA", padding: "16px 24px 0px" } }}
styles={{ root: { backgroundColor: "#FAFAFA", padding: "8px" } }}
id="copilot-textfield-label"
>
<Stack horizontal>
<Image src={CopilotIcon} style={{ width: 24, height: 24 }} alt="Copilot" role="none" />
<Text style={{ marginLeft: 8, fontWeight: 600, fontSize: 16 }}>Copilot</Text>
<Stack
horizontal
styles={{
root: {
width: "100%",
borderWidth: 1,
borderStyle: "solid",
borderColor: "#D1D1D1",
borderRadius: 8,
boxShadow: "0px 4px 8px 0px #00000024",
},
}}
>
<Stack style={{ width: "100%" }}>
<Stack horizontal verticalAlign="center" style={{ padding: "8px 8px 0px 8px" }}>
<TextField
id="naturalLanguageInput"
value={userPrompt}
onChange={handleUserPromptChange}
onClick={() => {
inputEdited.current = true;
setShowSamplePrompts(true);
}}
onKeyDown={(e) => {
if (e.key === "Enter" && userPrompt) {
inputEdited.current = true;
startGenerateQueryProcess();
}
}}
style={{ lineHeight: 30 }}
styles={{
root: { width: "100%" },
suffix: {
background: "none",
padding: 0,
},
fieldGroup: {
borderRadius: 4,
borderColor: "#D1D1D1",
"::after": {
border: "inherit",
borderWidth: 2,
borderBottomColor: "#464FEB",
borderRadius: 4,
},
},
}}
disabled={isGeneratingQuery}
autoComplete="off"
placeholder="Ask a question in natural language and well generate the query for you."
aria-labelledby="copilot-textfield-label"
onRenderSuffix={() => {
return (
<IconButton
iconProps={{ iconName: "Send" }}
disabled={isGeneratingQuery || !userPrompt.trim()}
style={{ background: "none" }}
onClick={() => startGenerateQueryProcess()}
aria-label="Send"
/>
);
}}
/>
{showPromptTeachingBubble && copilotTeachingBubbleVisible && (
<TeachingBubble
calloutProps={{ directionalHint: DirectionalHint.bottomCenter }}
target="#naturalLanguageInput"
hasCloseButton={true}
closeButtonAriaLabel="Close"
onDismiss={() => toggleCopilotTeachingBubbleVisible(false)}
hasSmallHeadline={true}
headline="Write a prompt"
>
Write a prompt here and Copilot will generate the query for you. You can also choose from our{" "}
<Link
onClick={() => {
setShowSamplePrompts(true);
toggleCopilotTeachingBubbleVisible(false);
}}
style={{ color: "white", fontWeight: 600 }}
>
sample prompts
</Link>{" "}
or write your own query
</TeachingBubble>
)}
{showSamplePrompts && (
<Callout
styles={{ root: { minWidth: 400, maxWidth: "70vw" } }}
target="#naturalLanguageInput"
isBeakVisible={false}
onDismiss={() => setShowSamplePrompts(false)}
directionalHintFixed={true}
directionalHint={DirectionalHint.bottomLeftEdge}
alignTargetEdge={true}
gapSpace={4}
>
<Stack>
{filteredHistories?.length > 0 && (
<Stack>
<Text
style={{
width: "100%",
fontSize: 14,
fontWeight: 600,
color: "#0078D4",
marginLeft: 16,
padding: "4px 0",
}}
>
Recent
</Text>
{filteredHistories.map((history, i) => (
<DefaultButton
key={i}
onClick={() => {
setUserPrompt(history);
setShowSamplePrompts(false);
inputEdited.current = true;
}}
onRenderIcon={() => <Image src={RecentIcon} styles={{ root: { overflow: "unset" } }} />}
styles={promptStyles}
>
{history}
</DefaultButton>
))}
</Stack>
)}
{filteredSuggestedPrompts?.length > 0 && (
<Stack>
<Text
style={{
width: "100%",
fontSize: 14,
fontWeight: 600,
color: "#0078D4",
marginLeft: 16,
padding: "4px 0",
}}
>
Suggested Prompts
</Text>
{filteredSuggestedPrompts.map((prompt) => (
<DefaultButton
key={prompt.id}
onClick={() => {
setUserPrompt(prompt.text);
setShowSamplePrompts(false);
inputEdited.current = true;
}}
onRenderIcon={() => <Image src={HintIcon} />}
styles={promptStyles}
>
{prompt.text}
</DefaultButton>
))}
</Stack>
)}
{(filteredHistories?.length > 0 || filteredSuggestedPrompts?.length > 0) && (
<Stack>
<Separator
styles={{
root: {
selectors: { "::before": { background: "#E1DFDD" } },
padding: 0,
},
}}
/>
<Text
style={{
width: "100%",
fontSize: 14,
marginLeft: 16,
padding: "4px 0",
}}
>
Learn about{" "}
<Link target="_blank" href="https://aka.ms/cdb-copilot-writing">
writing effective prompts
</Link>
</Text>
</Stack>
)}
</Stack>
</Callout>
)}
</Stack>
{!isGeneratingQuery && (
<Stack style={{ padding: 8 }}>
{!showFeedbackBar && (
<Text style={{ fontSize: 12 }}>
AI-generated content can have mistakes. Make sure it&apos;s accurate and appropriate before using it.{" "}
<Link href="https://aka.ms/cdb-copilot-preview-terms" target="_blank" style={{ color: "#0072D4" }}>
Read preview terms
</Link>
{showErrorMessageBar && (
<MessageBar messageBarType={MessageBarType.error}>
{errorMessage ? errorMessage : "We ran into an error and were not able to execute query."}
</MessageBar>
)}
{showInvalidQueryMessageBar && (
<MessageBar
messageBarType={MessageBarType.info}
styles={{ root: { backgroundColor: "#F0F6FF" }, icon: { color: "#015CDA" } }}
>
We were unable to generate a query based upon the prompt provided. Please modify the prompt and
try again. For examples of how to write a good prompt, please read
<Link href="https://aka.ms/cdb-copilot-writing" target="_blank">
this article.
</Link>{" "}
Our content guidelines can be found
<Link href="https://aka.ms/cdb-query-copilot" target="_blank">
here.
</Link>
</MessageBar>
)}
</Text>
)}
{showFeedbackBar && (
<Stack horizontal verticalAlign="center" style={{ maxHeight: 20 }}>
{userContext.feedbackPolicies?.policyAllowFeedback && (
<Stack horizontal verticalAlign="center">
<Text style={{ fontSize: 12 }}>Provide feedback</Text>
{showCallout && !hideFeedbackModalForLikedQueries && (
<Callout
role="status"
style={{ padding: "6px 12px" }}
styles={{
root: {
borderRadius: 8,
},
beakCurtain: {
borderRadius: 8,
},
calloutMain: {
borderRadius: 8,
},
}}
target="#likeBtn"
onDismiss={() => {
setShowCallout(false);
SubmitFeedback({
params: {
generatedQuery: generatedQuery,
likeQuery: likeQuery,
description: "",
userPrompt: userPrompt,
},
explorer,
databaseId,
containerId,
mode: isSampleCopilotActive ? "Sample" : "User",
});
}}
directionalHint={DirectionalHint.topCenter}
>
<Text>
Thank you. Need to give{" "}
<Link
onClick={() => {
setShowCallout(false);
openFeedbackModal(generatedQuery, true, userPrompt);
}}
>
more feedback?
</Link>
</Text>
</Callout>
)}
<IconButton
id="likeBtn"
style={{ marginLeft: 10 }}
aria-label="Like"
role="toggle"
iconProps={{ iconName: likeQuery === true ? "LikeSolid" : "Like" }}
onClick={() => {
setShowCallout(!likeQuery);
setLikeQuery(!likeQuery);
if (likeQuery === true) {
document.getElementById("likeStatus").innerHTML = "Unpressed";
}
if (likeQuery === false) {
document.getElementById("likeStatus").innerHTML = "Liked";
}
if (dislikeQuery) {
setDislikeQuery(!dislikeQuery);
}
}}
/>
<IconButton
style={{ margin: "0 4px" }}
role="toggle"
aria-label="Dislike"
iconProps={{ iconName: dislikeQuery === true ? "DislikeSolid" : "Dislike" }}
onClick={() => {
let toggleStatusValue = "Unpressed";
if (!dislikeQuery) {
openFeedbackModal(generatedQuery, false, userPrompt);
setLikeQuery(false);
toggleStatusValue = "Disliked";
}
setDislikeQuery(!dislikeQuery);
setShowCallout(false);
document.getElementById("likeStatus").innerHTML = toggleStatusValue;
}}
/>
<span role="status" style={{ position: "absolute", left: "-9999px" }} id="likeStatus"></span>
<Separator
vertical
styles={{
root: {
"::after": {
backgroundColor: "#767676",
},
},
}}
/>
</Stack>
)}
<CommandBarButton
className="copyQuery"
onClick={copyGeneratedCode}
iconProps={{ iconName: "Copy" }}
style={{ fontSize: 12, transition: "background-color 0.3s ease", height: "100%" }}
styles={{
root: {
backgroundColor: "inherit",
},
}}
>
Copy code
</CommandBarButton>
<CommandBarButton
className="deleteQuery"
onClick={() => {
setShowDeletePopup(true);
}}
iconProps={{ iconName: "Delete" }}
style={{ fontSize: 12, transition: "background-color 0.3s ease", height: "100%" }}
styles={{
root: {
backgroundColor: "inherit",
},
}}
>
Clear editor
</CommandBarButton>
</Stack>
)}
</Stack>
)}
{isGeneratingQuery && (
<ProgressIndicator
label="Thinking..."
ariaLabel={getAriaLabel()}
barHeight={4}
styles={{
root: {
fontSize: 12,
width: "100%",
bottom: 0,
},
itemName: {
padding: "0px 8px",
},
itemProgress: {
borderBottomLeftRadius: 8,
borderBottomRightRadius: 8,
padding: 0,
},
progressBar: {
backgroundImage:
"linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgb(24, 90, 189) 35%, rgb(71, 207, 250) 70%, rgb(180, 124, 248) 92%, rgba(0, 0, 0, 0))",
animationDuration: "5s",
},
}}
/>
)}
</Stack>
<IconButton
iconProps={{ imageProps: { src: errorIcon } }}
onClick={() => {
@@ -323,307 +698,10 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
clearFeedback();
resetMessageStates();
}}
styles={{
root: {
marginLeft: "auto !important",
},
}}
ariaLabel="Close"
title="Close copilot"
/>
</Stack>
<Stack horizontal verticalAlign="center">
<TextField
id="naturalLanguageInput"
value={userPrompt}
onChange={handleUserPromptChange}
onClick={() => {
inputEdited.current = true;
setShowSamplePrompts(true);
}}
onKeyDown={(e) => {
if (e.key === "Enter" && userPrompt) {
inputEdited.current = true;
startGenerateQueryProcess();
}
}}
style={{ lineHeight: 30 }}
styles={{ root: { width: "95%" }, fieldGroup: { borderRadius: 6 } }}
disabled={isGeneratingQuery}
autoComplete="off"
placeholder="Ask a question in natural language and well generate the query for you."
aria-labelledby="copilot-textfield-label"
/>
{showPromptTeachingBubble && copilotTeachingBubbleVisible && (
<TeachingBubble
calloutProps={{ directionalHint: DirectionalHint.bottomCenter }}
target="#naturalLanguageInput"
hasCloseButton={true}
closeButtonAriaLabel="Close"
onDismiss={() => toggleCopilotTeachingBubbleVisible(false)}
hasSmallHeadline={true}
headline="Write a prompt"
>
Write a prompt here and Copilot will generate the query for you. You can also choose from our{" "}
<Link
onClick={() => {
setShowSamplePrompts(true);
toggleCopilotTeachingBubbleVisible(false);
}}
style={{ color: "white", fontWeight: 600 }}
>
sample prompts
</Link>{" "}
or write your own query
</TeachingBubble>
)}
<IconButton
iconProps={{ iconName: "Send" }}
disabled={isGeneratingQuery || !userPrompt.trim()}
style={{ marginLeft: 8 }}
onClick={() => startGenerateQueryProcess()}
aria-label="Send"
/>
<div role="alert" aria-label={getAriaLabel()}>
{isGeneratingQuery && <Spinner style={{ marginLeft: 8 }} />}
</div>
{showSamplePrompts && (
<Callout
styles={{ root: { minWidth: 400, maxWidth: "70vw" } }}
target="#naturalLanguageInput"
isBeakVisible={false}
onDismiss={() => setShowSamplePrompts(false)}
directionalHintFixed={true}
directionalHint={DirectionalHint.bottomLeftEdge}
alignTargetEdge={true}
gapSpace={4}
>
<Stack>
{filteredHistories?.length > 0 && (
<Stack>
<Text
style={{
width: "100%",
fontSize: 14,
fontWeight: 600,
color: "#0078D4",
marginLeft: 16,
padding: "4px 0",
}}
>
Recent
</Text>
{filteredHistories.map((history, i) => (
<DefaultButton
key={i}
onClick={() => {
setUserPrompt(history);
setShowSamplePrompts(false);
inputEdited.current = true;
}}
onRenderIcon={() => <Image src={RecentIcon} styles={{ root: { overflow: "unset" } }} />}
styles={promptStyles}
>
{history}
</DefaultButton>
))}
</Stack>
)}
{filteredSuggestedPrompts?.length > 0 && (
<Stack>
<Text
style={{
width: "100%",
fontSize: 14,
fontWeight: 600,
color: "#0078D4",
marginLeft: 16,
padding: "4px 0",
}}
>
Suggested Prompts
</Text>
{filteredSuggestedPrompts.map((prompt) => (
<DefaultButton
key={prompt.id}
onClick={() => {
setUserPrompt(prompt.text);
setShowSamplePrompts(false);
inputEdited.current = true;
}}
onRenderIcon={() => <Image src={HintIcon} />}
styles={promptStyles}
>
{prompt.text}
</DefaultButton>
))}
</Stack>
)}
{(filteredHistories?.length > 0 || filteredSuggestedPrompts?.length > 0) && (
<Stack>
<Separator
styles={{
root: {
selectors: { "::before": { background: "#E1DFDD" } },
padding: 0,
},
}}
/>
<Text
style={{
width: "100%",
fontSize: 14,
marginLeft: 16,
padding: "4px 0",
}}
>
Learn about{" "}
<Link target="_blank" href="https://aka.ms/cdb-copilot-writing">
writing effective prompts
</Link>
</Text>
</Stack>
)}
</Stack>
</Callout>
)}
</Stack>
<Stack style={{ margin: "8px 0" }}>
<Text style={{ fontSize: 12 }}>
AI-generated content can have mistakes. Make sure it&apos;s accurate and appropriate before using it.{" "}
<Link href="https://aka.ms/cdb-copilot-preview-terms" target="_blank" style={{ color: "#0072D4" }}>
Read preview terms
</Link>
{showErrorMessageBar && (
<MessageBar messageBarType={MessageBarType.error}>
{errorMessage ? errorMessage : "We ran into an error and were not able to execute query."}
</MessageBar>
)}
{showInvalidQueryMessageBar && (
<MessageBar
messageBarType={MessageBarType.info}
styles={{ root: { backgroundColor: "#F0F6FF" }, icon: { color: "#015CDA" } }}
>
We were unable to generate a query based upon the prompt provided. Please modify the prompt and try again.
For examples of how to write a good prompt, please read
<Link href="https://aka.ms/cdb-copilot-writing" target="_blank">
this article.
</Link>{" "}
Our content guidelines can be found
<Link href="https://aka.ms/cdb-query-copilot" target="_blank">
here.
</Link>
</MessageBar>
)}
</Text>
</Stack>
{showFeedbackBar && (
<Stack
style={{ backgroundColor: "#FFF8F0", padding: "2px 8px", minHeight: 32 }}
horizontal
verticalAlign="center"
>
{userContext.feedbackPolicies?.policyAllowFeedback && (
<Stack horizontal verticalAlign="center">
<Text style={{ fontWeight: 600, fontSize: 12 }}>Provide feedback on the query generated</Text>
{showCallout && !hideFeedbackModalForLikedQueries && (
<Callout
role="status"
style={{ padding: 8 }}
target="#likeBtn"
onDismiss={() => {
setShowCallout(false);
SubmitFeedback({
params: {
generatedQuery: generatedQuery,
likeQuery: likeQuery,
description: "",
userPrompt: userPrompt,
},
explorer,
databaseId,
containerId,
mode: isSampleCopilotActive ? "Sample" : "User",
});
}}
directionalHint={DirectionalHint.topCenter}
>
<Text>
Thank you. Need to give{" "}
<Link
onClick={() => {
setShowCallout(false);
openFeedbackModal(generatedQuery, true, userPrompt);
}}
>
more feedback?
</Link>
</Text>
</Callout>
)}
<IconButton
id="likeBtn"
style={{ marginLeft: 20 }}
aria-label="Like"
role="toggle"
iconProps={{ iconName: likeQuery === true ? "LikeSolid" : "Like" }}
onClick={() => {
setShowCallout(!likeQuery);
setLikeQuery(!likeQuery);
if (likeQuery === true) {
document.getElementById("likeStatus").innerHTML = "Unpressed";
}
if (likeQuery === false) {
document.getElementById("likeStatus").innerHTML = "Liked";
}
if (dislikeQuery) {
setDislikeQuery(!dislikeQuery);
}
}}
/>
<IconButton
style={{ margin: "0 10px" }}
role="toggle"
aria-label="Dislike"
iconProps={{ iconName: dislikeQuery === true ? "DislikeSolid" : "Dislike" }}
onClick={() => {
let toggleStatusValue = "Unpressed";
if (!dislikeQuery) {
openFeedbackModal(generatedQuery, false, userPrompt);
setLikeQuery(false);
toggleStatusValue = "Disliked";
}
setDislikeQuery(!dislikeQuery);
setShowCallout(false);
document.getElementById("likeStatus").innerHTML = toggleStatusValue;
}}
/>
<span role="status" style={{ position: "absolute", left: "-9999px" }} id="likeStatus"></span>
<Separator vertical style={{ color: "#EDEBE9" }} />
</Stack>
)}
<CommandBarButton
className="copyQuery"
onClick={copyGeneratedCode}
iconProps={{ iconName: "Copy" }}
style={{ margin: "0 10px", backgroundColor: "#FFF8F0", transition: "background-color 0.3s ease" }}
>
Copy query
</CommandBarButton>
<CommandBarButton
className="deleteQuery"
onClick={() => {
setShowDeletePopup(true);
}}
iconProps={{ iconName: "Delete" }}
style={{ margin: "0 10px", backgroundColor: "#FFF8F0", transition: "background-color 0.3s ease" }}
>
Delete query
</CommandBarButton>
</Stack>
)}
{isSamplePromptsOpen && <SamplePrompts sampleProps={sampleProps} />}
{query !== "" && query.trim().length !== 0 && (
<DeletePopup

View File

@@ -104,15 +104,16 @@ export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: Query
)}
<Stack className="tabPaneContentContainer">
<SplitterLayout percentage={true} vertical={true} primaryIndex={0} primaryMinSize={30} secondaryMinSize={70}>
COPILOT
<EditorReact
language={"sql"}
content={query}
isReadOnly={false}
wordWrap={"on"}
ariaLabel={"Editing Query"}
lineNumbers={"on"}
onContentChanged={(newQuery: string) => setQuery(newQuery)}
onContentSelected={(selectedQuery: string) => setSelectedQuery(selectedQuery)}
language={"sql"}
content={query}
isReadOnly={false}
wordWrap={"on"}
ariaLabel={"Editing Query"}
lineNumbers={"on"}
onContentChanged={(newQuery: string) => setQuery(newQuery)}
onContentSelected={(selectedQuery: string) => setSelectedQuery(selectedQuery)}
/>
<QueryCopilotResults />
</SplitterLayout>

View File

@@ -6,16 +6,16 @@ import DiscardIcon from "../../../images/discard.svg";
import SaveIcon from "../../../images/save-cosmos.svg";
import * as Constants from "../../Common/Constants";
import { DocumentsGridMetrics, KeyCodes } from "../../Common/Constants";
import { createDocument } from "../../Common/dataAccess/createDocument";
import { deleteConflict } from "../../Common/dataAccess/deleteConflict";
import { deleteDocument } from "../../Common/dataAccess/deleteDocument";
import { queryConflicts } from "../../Common/dataAccess/queryConflicts";
import { updateDocument } from "../../Common/dataAccess/updateDocument";
import editable from "../../Common/EditableUtility";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import * as HeadersUtility from "../../Common/HeadersUtility";
import { MinimalQueryIterator } from "../../Common/IteratorUtilities";
import { Splitter, SplitterBounds, SplitterDirection } from "../../Common/Splitter";
import { createDocument } from "../../Common/dataAccess/createDocument";
import { deleteConflict } from "../../Common/dataAccess/deleteConflict";
import { deleteDocument } from "../../Common/dataAccess/deleteDocument";
import { queryConflicts } from "../../Common/dataAccess/queryConflicts";
import { updateDocument } from "../../Common/dataAccess/updateDocument";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
@@ -621,6 +621,7 @@ export default class ConflictsTab extends TabsBase {
buttons.push({
iconSrc: DiscardIcon,
iconAlt: label,
keyboardShortcut: "DISCARD",
onCommandClick: this.onDiscardClick,
commandButtonLabel: label,
ariaLabel: label,

View File

@@ -921,6 +921,7 @@ export default class DocumentsTab extends TabsBase {
buttons.push({
iconSrc: DiscardIcon,
iconAlt: label,
keyboardShortcut: "DISCARD",
onCommandClick: this.onRevertNewDocumentClick,
commandButtonLabel: label,
ariaLabel: label,
@@ -950,6 +951,7 @@ export default class DocumentsTab extends TabsBase {
buttons.push({
iconSrc: DiscardIcon,
iconAlt: label,
keyboardShortcut: "DISCARD",
onCommandClick: this.onRevertExisitingDocumentClick,
commandButtonLabel: label,
ariaLabel: label,

View File

@@ -3,6 +3,8 @@
import { FeedOptions, QueryOperationOptions } from "@azure/cosmos";
import { Platform, configContext } from "ConfigContext";
import { useDialog } from "Explorer/Controls/Dialog";
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
import { MonacoNamespace, monaco } from "Explorer/LazyMonaco";
import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal";
import { useCopilotStore } from "Explorer/QueryCopilot/QueryCopilotContext";
import { QueryCopilotPromptbar } from "Explorer/QueryCopilot/QueryCopilotPromptbar";
@@ -39,7 +41,6 @@ import { userContext } from "../../../UserContext";
import * as QueryUtils from "../../../Utils/QueryUtils";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import { EditorReact } from "../../Controls/Editor/EditorReact";
import Explorer from "../../Explorer";
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane";
@@ -468,6 +469,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
buttons.push({
iconSrc: CancelQueryIcon,
iconAlt: label,
keyboardShortcut: "CANCEL_QUERY",
onCommandClick: () => this.queryAbortController.abort(),
commandButtonLabel: label,
ariaLabel: label,
@@ -584,6 +586,15 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
}
private getEditorAndQueryResult(): JSX.Element {
const configureEditor = (monaco: MonacoNamespace, editor: monaco.editor.IStandaloneCodeEditor) => {
editor.addAction({
id: "execute-query",
label: "Execute Query",
keybindings: [monaco.KeyMod.Shift | monaco.KeyCode.Enter],
run: () => this.onExecuteQueryClick(),
});
}
return (
<Fragment>
<div className="tab-pane" id={this.props.tabId} role="tabpanel">
@@ -608,7 +619,8 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
lineNumbers={"on"}
onContentChanged={(newContent: string) => this.onChangeContent(newContent)}
onContentSelected={(selectedContent: string) => this.onSelectedContent(selectedContent)}
/>
configureEditor={configureEditor}
/>;
</div>
</Fragment>
{this.props.isSampleCopilotActive ? (

View File

@@ -238,6 +238,7 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
buttons.push({
iconSrc: DiscardIcon,
iconAlt: label,
keyboardShortcut: "DISCARD",
onCommandClick: this.onDiscard,
commandButtonLabel: label,
ariaLabel: label,

View File

@@ -347,6 +347,7 @@ export default class StoredProcedureTabComponent extends React.Component<
buttons.push({
iconSrc: DiscardIcon,
iconAlt: label,
keyboardShortcut: "DISCARD",
onCommandClick: this.onDiscard,
commandButtonLabel: label,
ariaLabel: label,

View File

@@ -40,11 +40,10 @@ export default class TabsBase extends WaitsForTemplateViewModel {
this.database = options.database;
this.rid = options.rid || (this.collection && this.collection.rid) || "";
this.tabKind = options.tabKind;
this.tabTitle = ko.observable<string>(options.title);
this.tabTitle = ko.observable<string>(this.getTitle(options));
this.tabPath =
ko.observable(options.tabPath ?? "") ||
(this.collection &&
ko.observable<string>(`${this.collection.databaseId}>${this.collection.id()}>${this.tabTitle()}`));
this.collection &&
ko.observable<string>(`${this.collection.databaseId}>${this.collection.id()}>${options.title}`);
this.pendingNotification = ko.observable<DataModels.Notification>(undefined);
this.onLoadStartKey = options.onLoadStartKey;
this.closeTabButton = {
@@ -143,6 +142,26 @@ export default class TabsBase extends WaitsForTemplateViewModel {
return (this.collection && this.collection.container) || (this.database && this.database.container);
}
public getTitle(options: ViewModels.TabOptions): string {
const coll = this.collection?.id();
const db = this.database?.id();
if (coll) {
if (coll.length > 8) {
return coll.slice(0, 5) + "…" + options.title;
} else {
return coll + "." + options.title;
}
} else if (db) {
if (db.length > 8) {
return db.slice(0, 5) + "…" + options.title;
} else {
return db + "." + options.title;
}
} else {
return options.title;
}
}
/** Renders a Javascript object to be displayed inside Monaco Editor */
public renderObjectForEditor(value: any, replacer: any, space: string | number): string {
return JSON.stringify(value, replacer, space);

View File

@@ -256,6 +256,7 @@ export class TriggerTabContent extends Component<TriggerTab, ITriggerTabContentS
...this,
iconSrc: DiscardIcon,
iconAlt: label,
keyboardShortcut: "DISCARD",
onCommandClick: this.onDiscard,
commandButtonLabel: label,
ariaLabel: label,

View File

@@ -4,9 +4,9 @@ import React, { Component } from "react";
import DiscardIcon from "../../../images/discard.svg";
import SaveIcon from "../../../images/save-cosmos.svg";
import * as Constants from "../../Common/Constants";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { createUserDefinedFunction } from "../../Common/dataAccess/createUserDefinedFunction";
import { updateUserDefinedFunction } from "../../Common/dataAccess/updateUserDefinedFunction";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
@@ -109,6 +109,7 @@ export default class UserDefinedFunctionTabContent extends Component<
...this,
iconSrc: DiscardIcon,
iconAlt: label,
keyboardShortcut: "DISCARD",
onCommandClick: this.onDiscard,
commandButtonLabel: label,
ariaLabel: label,

View File

@@ -308,7 +308,7 @@ export default class Collection implements ViewModels.Collection {
collectionName: this.id(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.rawDataModel.id + " - Items",
tabTitle: "Items",
});
this.documentIds([]);
@@ -316,7 +316,7 @@ export default class Collection implements ViewModels.Collection {
partitionKey: this.partitionKey,
documentIds: ko.observableArray<DocumentId>([]),
tabKind: ViewModels.CollectionTabKind.Documents,
title: this.rawDataModel.id + " - Items",
title: "Items",
collection: this,
node: this,
tabPath: `${this.databaseId}>${this.id()}>Documents`,

View File

@@ -18,6 +18,7 @@ import "../externals/jquery.typeahead.min.js";
// Image Dependencies
import { Platform } from "ConfigContext";
import { QueryCopilotCarousel } from "Explorer/QueryCopilot/CopilotCarousel";
import * as ReactHotkeys from "react-hotkeys";
import "../images/CosmosDB_rgb_ui_lighttheme.ico";
import hdeConnectImage from "../images/HdeConnectCosmosDB.svg";
import "../images/favicon.ico";
@@ -61,6 +62,28 @@ import { useKnockoutExplorer } from "./hooks/useKnockoutExplorer";
initializeIcons();
const tagsIgnoredByReactHotkeys = ["INPUT", "SELECT"];
ReactHotkeys.configure({
ignoreEventsCondition: (evt) => {
// The default react-hotkeys behavior is to ignore events targetting a textarea, but we want the monaco editor's key events to bubble up
// So, we configure it to ignore all events targetting a textarea except when the target is a monaco editor's text area
if (!(evt.target instanceof HTMLElement)) {
return true;
}
if (tagsIgnoredByReactHotkeys.includes(evt.target.tagName)) {
return true;
}
if (evt.target.tagName === "TEXTAREA" && !evt.target.matches(".monaco-editor textarea")) {
return true;
}
return false;
}
})
const App: React.FunctionComponent = () => {
const [isLeftPaneExpanded, setIsLeftPaneExpanded] = useState<boolean>(true);
const isCarouselOpen = useCarousel((state) => state.shouldOpen);

View File

@@ -1,10 +1,11 @@
import { useBoolean } from "@fluentui/react-hooks";
import { userContext } from "UserContext";
import { usePortalBackendEndpoint } from "Utils/EndpointUtils";
import * as React from "react";
import ConnectImage from "../../../../images/HdeConnectCosmosDB.svg";
import ErrorImage from "../../../../images/error.svg";
import { AuthType } from "../../../AuthType";
import { HttpHeaders } from "../../../Common/Constants";
import { BackendApi, HttpHeaders } from "../../../Common/Constants";
import { configContext } from "../../../ConfigContext";
import { GenerateTokenResponse } from "../../../Contracts/DataModels";
import { isResourceTokenConnectionString } from "../Helpers/ResourceTokenUtils";
@@ -18,6 +19,23 @@ interface Props {
}
export const fetchEncryptedToken = async (connectionString: string): Promise<string> => {
if (!usePortalBackendEndpoint(BackendApi.GenerateToken)) {
return await fetchEncryptedToken_ToBeDeprecated(connectionString);
}
const headers = new Headers();
headers.append(HttpHeaders.connectionString, connectionString);
const url = configContext.PORTAL_BACKEND_ENDPOINT + "/api/connectionstring/token/generatetoken";
const response = await fetch(url, { headers, method: "POST" });
if (!response.ok) {
throw response;
}
const encryptedTokenResponse: string = await response.json();
return decodeURIComponent(encryptedTokenResponse);
};
export const fetchEncryptedToken_ToBeDeprecated = async (connectionString: string): Promise<string> => {
const headers = new Headers();
headers.append(HttpHeaders.connectionString, connectionString);
const url = configContext.BACKEND_ENDPOINT + "/api/guest/tokens/generateToken";

View File

@@ -51,6 +51,7 @@ interface FabricContext {
connectionId: string;
databaseConnectionInfo: FabricDatabaseConnectionInfo | undefined;
isReadOnly: boolean;
isVisible: boolean;
}
export type AdminFeedbackControlPolicy =

View File

@@ -1,4 +1,11 @@
import { CassandraProxyEndpoints, JunoEndpoints, MongoProxyEndpoints } from "Common/Constants";
import {
BackendApi,
CassandraProxyEndpoints,
JunoEndpoints,
MongoProxyEndpoints,
PortalBackendEndpoints,
} from "Common/Constants";
import { configContext } from "ConfigContext";
import * as Logger from "../Common/Logger";
export function validateEndpoint(
@@ -137,3 +144,9 @@ export const allowedJunoOrigins: ReadonlyArray<string> = [
];
export const allowedNotebookServerUrls: ReadonlyArray<string> = [];
export function usePortalBackendEndpoint(backendApi: BackendApi): boolean {
const activePortalBackendEndpoints: string[] = [PortalBackendEndpoints.Development];
const activeBackendApi: boolean = configContext.NEW_BACKEND_APIS?.includes(backendApi) || false;
return activeBackendApi && activePortalBackendEndpoints.includes(configContext.PORTAL_BACKEND_ENDPOINT as string);
}

View File

@@ -2,7 +2,6 @@ import { createUri } from "Common/UrlUtility";
import { DATA_EXPLORER_RPC_VERSION } from "Contracts/DataExplorerMessagesContract";
import { FABRIC_RPC_VERSION, FabricMessageV2 } from "Contracts/FabricMessagesContract";
import Explorer from "Explorer/Explorer";
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
import { useSelectedNode } from "Explorer/useSelectedNode";
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
@@ -90,6 +89,7 @@ async function configureFabric(): Promise<Explorer> {
// These are the versions of Fabric that Data Explorer supports.
const SUPPORTED_FABRIC_VERSIONS = [FABRIC_RPC_VERSION];
let firstContainerOpened = false;
let explorer: Explorer;
return new Promise<Explorer>((resolve) => {
window.addEventListener(
@@ -121,7 +121,10 @@ async function configureFabric(): Promise<Explorer> {
await scheduleRefreshDatabaseResourceToken(true);
resolve(explorer);
await explorer.refreshAllDatabases();
openFirstContainer(explorer, userContext.fabricContext.databaseConnectionInfo.databaseId);
if (userContext.fabricContext.isVisible && !firstContainerOpened) {
firstContainerOpened = true;
openFirstContainer(explorer, userContext.fabricContext.databaseConnectionInfo.databaseId);
}
break;
}
case "newContainer":
@@ -132,8 +135,16 @@ async function configureFabric(): Promise<Explorer> {
handleCachedDataMessage(data);
break;
}
case "setToolbarStatus": {
useCommandBar.getState().setIsHidden(data.message.visible === false);
case "explorerVisible": {
userContext.fabricContext.isVisible = data.message.visible;
if (
userContext.fabricContext.isVisible &&
!firstContainerOpened &&
userContext?.fabricContext?.databaseConnectionInfo?.databaseId !== undefined
) {
firstContainerOpened = true;
openFirstContainer(explorer, userContext.fabricContext.databaseConnectionInfo.databaseId);
}
break;
}
default:
@@ -327,12 +338,13 @@ function configureHostedWithResourceToken(config: ResourceToken): Explorer {
return explorer;
}
function createExplorerFabric(params: { connectionId: string }): Explorer {
function createExplorerFabric(params: { connectionId: string; isVisible: boolean }): Explorer {
updateUserContext({
fabricContext: {
connectionId: params.connectionId,
databaseConnectionInfo: undefined,
isReadOnly: true,
isVisible: params.isVisible ?? true,
},
authType: AuthType.ConnectionString,
databaseAccount: {