mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-07-04 11:03:50 +01:00
Save multiple query tab histories
This commit is contained in:
parent
e448e8df6b
commit
f7dffa4183
@ -51,6 +51,8 @@ export interface OpenCollectionTab extends OpenTab {
|
|||||||
*/
|
*/
|
||||||
export interface OpenQueryTab extends OpenCollectionTab {
|
export interface OpenQueryTab extends OpenCollectionTab {
|
||||||
query: QueryInfo;
|
query: QueryInfo;
|
||||||
|
splitterDirection?: "vertical" | "horizontal";
|
||||||
|
queryViewSizePercent?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -115,7 +115,13 @@ export interface CollectionBase extends TreeNode {
|
|||||||
isSampleCollection?: boolean;
|
isSampleCollection?: boolean;
|
||||||
|
|
||||||
onDocumentDBDocumentsClick(): void;
|
onDocumentDBDocumentsClick(): void;
|
||||||
onNewQueryClick(source: any, event?: MouseEvent, queryText?: string): void;
|
onNewQueryClick(
|
||||||
|
source: any,
|
||||||
|
event?: MouseEvent,
|
||||||
|
queryText?: string,
|
||||||
|
stringsplitterDirection?: "horizontal" | "vertical",
|
||||||
|
queryViewSizePercent?: number,
|
||||||
|
): void;
|
||||||
expandCollection(): void;
|
expandCollection(): void;
|
||||||
collapseCollection(): void;
|
collapseCollection(): void;
|
||||||
getDatabase(): Database;
|
getDatabase(): Database;
|
||||||
@ -309,6 +315,8 @@ export interface QueryTabOptions extends TabOptions {
|
|||||||
partitionKey?: DataModels.PartitionKey;
|
partitionKey?: DataModels.PartitionKey;
|
||||||
queryText?: string;
|
queryText?: string;
|
||||||
resourceTokenPartitionKey?: string;
|
resourceTokenPartitionKey?: string;
|
||||||
|
stringsplitterDirection?: "horizontal" | "vertical";
|
||||||
|
queryViewSizePercent?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScriptTabOption extends TabOptions {
|
export interface ScriptTabOption extends TabOptions {
|
||||||
|
@ -4,11 +4,15 @@ import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
|
|||||||
import { Environment, getEnvironment } from "Common/EnvironmentUtility";
|
import { Environment, getEnvironment } from "Common/EnvironmentUtility";
|
||||||
import { sendMessage } from "Common/MessageHandler";
|
import { sendMessage } from "Common/MessageHandler";
|
||||||
import { Platform, configContext } from "ConfigContext";
|
import { Platform, configContext } from "ConfigContext";
|
||||||
|
import { DataExplorerAction } from "Contracts/ActionContracts";
|
||||||
import { MessageTypes } from "Contracts/ExplorerContracts";
|
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||||
|
import { handleOpenAction } from "Explorer/OpenActions/OpenActions";
|
||||||
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
|
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
|
||||||
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||||
|
import { OPEN_TABS_SUBCOMPONENT_NAME } from "Explorer/Tabs/QueryTab/QueryTabStateUtil";
|
||||||
import { IGalleryItem } from "Juno/JunoClient";
|
import { IGalleryItem } from "Juno/JunoClient";
|
||||||
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
|
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
|
||||||
|
import { AppStateComponentNames, readSubComponentState } from "Shared/AppStatePersistenceUtility";
|
||||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||||
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
|
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
|
||||||
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
|
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
|
||||||
@ -1134,7 +1138,7 @@ export default class Explorer {
|
|||||||
if (userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo") {
|
if (userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo") {
|
||||||
userContext.authType === AuthType.ResourceToken
|
userContext.authType === AuthType.ResourceToken
|
||||||
? this.refreshDatabaseForResourceToken()
|
? this.refreshDatabaseForResourceToken()
|
||||||
: this.refreshAllDatabases();
|
: await this.refreshAllDatabases();
|
||||||
}
|
}
|
||||||
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
|
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
|
||||||
|
|
||||||
@ -1159,6 +1163,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.refreshSampleData();
|
await this.refreshSampleData();
|
||||||
|
this.restoreOpenTabs();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async configureCopilot(): Promise<void> {
|
public async configureCopilot(): Promise<void> {
|
||||||
@ -1205,4 +1210,19 @@ export default class Explorer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private restoreOpenTabs() {
|
||||||
|
const openTabsState = readSubComponentState<DataExplorerAction[]>(
|
||||||
|
AppStateComponentNames.DataExplorerAction,
|
||||||
|
OPEN_TABS_SUBCOMPONENT_NAME,
|
||||||
|
undefined,
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
openTabsState.forEach((openTabState) => {
|
||||||
|
if (openTabState) {
|
||||||
|
handleOpenAction(openTabState, useDatabases.getState().databases, this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,10 +121,13 @@ function openCollectionTab(
|
|||||||
action.tabKind === ActionContracts.TabKind.SQLQuery ||
|
action.tabKind === ActionContracts.TabKind.SQLQuery ||
|
||||||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLQuery]
|
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLQuery]
|
||||||
) {
|
) {
|
||||||
|
const openQueryTabAction = action as ActionContracts.OpenQueryTab;
|
||||||
collection.onNewQueryClick(
|
collection.onNewQueryClick(
|
||||||
collection,
|
collection,
|
||||||
undefined,
|
undefined,
|
||||||
generateQueryText(action as ActionContracts.OpenQueryTab, collection.partitionKeyProperties),
|
generateQueryText(openQueryTabAction, collection.partitionKeyProperties),
|
||||||
|
openQueryTabAction.splitterDirection,
|
||||||
|
openQueryTabAction.queryViewSizePercent,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,8 @@ export class NewQueryTab extends TabsBase {
|
|||||||
tabsBaseInstance: this,
|
tabsBaseInstance: this,
|
||||||
queryText: options.queryText,
|
queryText: options.queryText,
|
||||||
partitionKey: this.partitionKey,
|
partitionKey: this.partitionKey,
|
||||||
|
stringsplitterDirection: options.stringsplitterDirection,
|
||||||
|
queryViewSizePercent: options.queryViewSizePercent,
|
||||||
container: this.props.container,
|
container: this.props.container,
|
||||||
onTabAccessor: (instance: ITabAccessor): void => {
|
onTabAccessor: (instance: ITabAccessor): void => {
|
||||||
this.iTabAccessor = instance;
|
this.iTabAccessor = instance;
|
||||||
|
@ -13,26 +13,13 @@ import { readCopilotToggleStatus, saveCopilotToggleStatus } from "Explorer/Query
|
|||||||
import { OnExecuteQueryClick, QueryDocumentsPerPage } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
import { OnExecuteQueryClick, QueryDocumentsPerPage } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||||
import { QueryCopilotSidebar } from "Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar";
|
import { QueryCopilotSidebar } from "Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar";
|
||||||
import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection";
|
import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection";
|
||||||
import {
|
import { deleteQueryTabState, saveQueryTabState } from "Explorer/Tabs/QueryTab/QueryTabStateUtil";
|
||||||
OpenTabIndexRetriever,
|
|
||||||
QueryTexts,
|
|
||||||
SubComponentName,
|
|
||||||
deleteQueryTabSubComponentState,
|
|
||||||
readQueryTabSubComponentState,
|
|
||||||
saveQueryTabSubComponentState,
|
|
||||||
} from "Explorer/Tabs/QueryTab/QueryTabStateUtil";
|
|
||||||
import { QueryTabStyles, useQueryTabStyles } from "Explorer/Tabs/QueryTab/Styles";
|
import { QueryTabStyles, useQueryTabStyles } from "Explorer/Tabs/QueryTab/Styles";
|
||||||
import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil";
|
import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil";
|
||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
import { KeyboardAction } from "KeyboardShortcuts";
|
import { KeyboardAction } from "KeyboardShortcuts";
|
||||||
import { QueryConstants } from "Shared/Constants";
|
import { QueryConstants } from "Shared/Constants";
|
||||||
import {
|
import { LocalStorageUtility, StorageKey, getRUThreshold, ruThresholdEnabled } from "Shared/StorageUtility";
|
||||||
LocalStorageUtility,
|
|
||||||
StorageKey,
|
|
||||||
getDefaultQueryResultsView,
|
|
||||||
getRUThreshold,
|
|
||||||
ruThresholdEnabled,
|
|
||||||
} from "Shared/StorageUtility";
|
|
||||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||||
import { Allotment } from "allotment";
|
import { Allotment } from "allotment";
|
||||||
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
@ -107,6 +94,8 @@ export interface IQueryTabComponentProps {
|
|||||||
copilotEnabled?: boolean;
|
copilotEnabled?: boolean;
|
||||||
isSampleCopilotActive?: boolean;
|
isSampleCopilotActive?: boolean;
|
||||||
copilotStore?: Partial<QueryCopilotState>;
|
copilotStore?: Partial<QueryCopilotState>;
|
||||||
|
stringsplitterDirection?: "horizontal" | "vertical";
|
||||||
|
queryViewSizePercent?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IQueryTabStates {
|
interface IQueryTabStates {
|
||||||
@ -132,6 +121,8 @@ interface IQueryTabStates {
|
|||||||
export const QueryTabCopilotComponent = (props: IQueryTabComponentProps): any => {
|
export const QueryTabCopilotComponent = (props: IQueryTabComponentProps): any => {
|
||||||
const styles = useQueryTabStyles();
|
const styles = useQueryTabStyles();
|
||||||
const copilotStore = useCopilotStore();
|
const copilotStore = useCopilotStore();
|
||||||
|
const tabIndex = useTabs.getState().openedTabs.findIndex((tab) => tab.tabId === props.tabId);
|
||||||
|
|
||||||
const isSampleCopilotActive = useSelectedNode.getState().isQueryCopilotCollectionSelected();
|
const isSampleCopilotActive = useSelectedNode.getState().isQueryCopilotCollectionSelected();
|
||||||
const queryTabProps = {
|
const queryTabProps = {
|
||||||
...props,
|
...props,
|
||||||
@ -140,24 +131,25 @@ export const QueryTabCopilotComponent = (props: IQueryTabComponentProps): any =>
|
|||||||
(useQueryCopilot().copilotUserDBEnabled || (isSampleCopilotActive && !!userContext.sampleDataConnectionInfo)),
|
(useQueryCopilot().copilotUserDBEnabled || (isSampleCopilotActive && !!userContext.sampleDataConnectionInfo)),
|
||||||
isSampleCopilotActive: isSampleCopilotActive,
|
isSampleCopilotActive: isSampleCopilotActive,
|
||||||
copilotStore: copilotStore,
|
copilotStore: copilotStore,
|
||||||
|
tabIndex,
|
||||||
};
|
};
|
||||||
return <QueryTabComponentImpl styles={styles} {...queryTabProps}></QueryTabComponentImpl>;
|
return <QueryTabComponentImpl styles={styles} {...queryTabProps}></QueryTabComponentImpl>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const QueryTabComponent = (props: IQueryTabComponentProps): any => {
|
export const QueryTabComponent = (props: IQueryTabComponentProps): any => {
|
||||||
const styles = useQueryTabStyles();
|
const styles = useQueryTabStyles();
|
||||||
return <QueryTabComponentImpl styles={styles} {...props}></QueryTabComponentImpl>;
|
const tabIndex = useTabs.getState().openedTabs.findIndex((tab) => tab.tabId === props.tabId);
|
||||||
|
|
||||||
|
return <QueryTabComponentImpl styles={styles} {...{ ...props, tabIndex }}></QueryTabComponentImpl>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type QueryTabComponentImplProps = IQueryTabComponentProps & {
|
type QueryTabComponentImplProps = IQueryTabComponentProps & {
|
||||||
styles: QueryTabStyles;
|
styles: QueryTabStyles;
|
||||||
|
tabIndex: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Inner (legacy) class component. We only use this component via one of the two functional components above (since we need to use the `useQueryTabStyles` hook).
|
// Inner (legacy) class component. We only use this component via one of the two functional components above (since we need to use the `useQueryTabStyles` hook).
|
||||||
class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps, IQueryTabStates> {
|
class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps, IQueryTabStates> {
|
||||||
// This is a static data structure to keep track which index in persistence is for which tabId
|
|
||||||
private static openTabIndexRetriever = new OpenTabIndexRetriever();
|
|
||||||
|
|
||||||
private static readonly DEBOUNCE_DELAY_MS = 1000;
|
private static readonly DEBOUNCE_DELAY_MS = 1000;
|
||||||
|
|
||||||
public queryEditorId: string;
|
public queryEditorId: string;
|
||||||
@ -175,17 +167,11 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
|
|
||||||
constructor(props: QueryTabComponentImplProps) {
|
constructor(props: QueryTabComponentImplProps) {
|
||||||
super(props);
|
super(props);
|
||||||
QueryTabComponentImpl.openTabIndexRetriever.setOpenTabIndex(
|
|
||||||
props.collection.databaseId,
|
|
||||||
props.collection.id(),
|
|
||||||
props.tabId,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.queryEditor = createRef<EditorReact>();
|
this.queryEditor = createRef<EditorReact>();
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
toggleState: ToggleState.Result,
|
toggleState: ToggleState.Result,
|
||||||
sqlQueryEditorContent: this._getDefaultQueryEditorContent(props),
|
sqlQueryEditorContent: props.isPreferredApiMongoDB ? "{}" : props.queryText || "SELECT * FROM c",
|
||||||
selectedContent: "",
|
selectedContent: "",
|
||||||
queryResults: undefined,
|
queryResults: undefined,
|
||||||
errors: [],
|
errors: [],
|
||||||
@ -196,8 +182,9 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
cancelQueryTimeoutID: undefined,
|
cancelQueryTimeoutID: undefined,
|
||||||
copilotActive: this._queryCopilotActive(),
|
copilotActive: this._queryCopilotActive(),
|
||||||
currentTabActive: true,
|
currentTabActive: true,
|
||||||
queryResultsView: this._getDefaultQUeryResultsViewDirection(props),
|
queryResultsView:
|
||||||
queryViewSizePercent: this._getQueryViewSizePercent(props),
|
props.stringsplitterDirection === "horizontal" ? SplitterDirection.Horizontal : SplitterDirection.Vertical,
|
||||||
|
queryViewSizePercent: props.queryViewSizePercent,
|
||||||
};
|
};
|
||||||
this.isCloseClicked = false;
|
this.isCloseClicked = false;
|
||||||
this.splitterId = this.props.tabId + "_splitter";
|
this.splitterId = this.props.tabId + "_splitter";
|
||||||
@ -226,63 +213,33 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
onSaveClickEvent: this.getCurrentEditorQuery.bind(this),
|
onSaveClickEvent: this.getCurrentEditorQuery.bind(this),
|
||||||
onCloseClickEvent: this.onCloseClick.bind(this),
|
onCloseClickEvent: this.onCloseClick.bind(this),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update persistence
|
||||||
|
this.saveQueryTabStateDebounced();
|
||||||
|
// DO THIS IN useTabs.activateNewTab() INSTEAD
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to save the query text in the query tab state
|
* Helper function to save the query text in the query tab state
|
||||||
* Since it reads and writes to the same state, it is debounced
|
* Since it reads and writes to the same state, it is debounced
|
||||||
* @param collection
|
|
||||||
* @param queryText
|
|
||||||
* @param queryTabIndex
|
|
||||||
*/
|
*/
|
||||||
private saveQueryTextDebounced = (queryText: string) => {
|
private saveQueryTabStateDebounced = () => {
|
||||||
if (this.timeoutId) {
|
if (this.timeoutId) {
|
||||||
clearTimeout(this.timeoutId);
|
clearTimeout(this.timeoutId);
|
||||||
}
|
}
|
||||||
this.timeoutId = setTimeout(async () => {
|
this.timeoutId = setTimeout(async () => {
|
||||||
const queryTexts = readQueryTabSubComponentState<QueryTexts>(
|
saveQueryTabState(
|
||||||
SubComponentName.QueryText,
|
|
||||||
this.props.collection,
|
this.props.collection,
|
||||||
[],
|
{
|
||||||
|
queryText: this.state.sqlQueryEditorContent,
|
||||||
|
splitterDirection: this.state.queryResultsView,
|
||||||
|
queryViewSizePercent: this.state.queryViewSizePercent,
|
||||||
|
},
|
||||||
|
this.props.tabIndex,
|
||||||
);
|
);
|
||||||
|
|
||||||
const queryTextsIndex = QueryTabComponentImpl.openTabIndexRetriever.getOpenTabIndex(
|
|
||||||
this.props.collection.databaseId,
|
|
||||||
this.props.collection.id(),
|
|
||||||
this.props.tabId,
|
|
||||||
);
|
|
||||||
|
|
||||||
queryTexts[queryTextsIndex] = queryText;
|
|
||||||
saveQueryTabSubComponentState<QueryTexts>(SubComponentName.QueryText, this.props.collection, queryTexts);
|
|
||||||
}, QueryTabComponentImpl.DEBOUNCE_DELAY_MS);
|
}, QueryTabComponentImpl.DEBOUNCE_DELAY_MS);
|
||||||
};
|
};
|
||||||
|
|
||||||
private _getQueryViewSizePercent(props: QueryTabComponentImplProps): number {
|
|
||||||
return readQueryTabSubComponentState<number>(SubComponentName.QueryViewSizePercent, props.collection, 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _getDefaultQUeryResultsViewDirection(props: QueryTabComponentImplProps): SplitterDirection {
|
|
||||||
const defaultQueryResultsView = getDefaultQueryResultsView();
|
|
||||||
return readQueryTabSubComponentState<SplitterDirection>(
|
|
||||||
SubComponentName.SplitterDirection,
|
|
||||||
props.collection,
|
|
||||||
defaultQueryResultsView,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _getDefaultQueryEditorContent(props: QueryTabComponentImplProps): string {
|
|
||||||
const defaultText = props.isPreferredApiMongoDB ? "{}" : props.queryText || "SELECT * FROM c";
|
|
||||||
// Retrieve from app state if available
|
|
||||||
const queryTexts = readQueryTabSubComponentState<QueryTexts>(SubComponentName.QueryText, props.collection, []);
|
|
||||||
const queryTextsIndex = QueryTabComponentImpl.openTabIndexRetriever.getOpenTabIndex(
|
|
||||||
this.props.collection.databaseId,
|
|
||||||
this.props.collection.id(),
|
|
||||||
this.props.tabId,
|
|
||||||
);
|
|
||||||
|
|
||||||
return queryTexts[queryTextsIndex] || defaultText;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _queryCopilotActive(): boolean {
|
private _queryCopilotActive(): boolean {
|
||||||
if (this.props.copilotEnabled) {
|
if (this.props.copilotEnabled) {
|
||||||
return readCopilotToggleStatus(userContext.databaseAccount);
|
return readCopilotToggleStatus(userContext.databaseAccount);
|
||||||
@ -643,14 +600,7 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
private _setViewLayout(direction: SplitterDirection): void {
|
private _setViewLayout(direction: SplitterDirection): void {
|
||||||
this.setState({ queryResultsView: direction });
|
this.setState({ queryResultsView: direction }, () => this.saveQueryTabStateDebounced());
|
||||||
|
|
||||||
// Store to local storage
|
|
||||||
saveQueryTabSubComponentState<SplitterDirection>(
|
|
||||||
SubComponentName.SplitterDirection,
|
|
||||||
this.props.collection,
|
|
||||||
direction,
|
|
||||||
);
|
|
||||||
|
|
||||||
// We'll need to refresh the context buttons to update the selected state of the view buttons
|
// We'll need to refresh the context buttons to update the selected state of the view buttons
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -682,13 +632,16 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
if (this.state.copilotActive) {
|
if (this.state.copilotActive) {
|
||||||
this.props.copilotStore?.setQuery(newContent);
|
this.props.copilotStore?.setQuery(newContent);
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState(
|
||||||
|
{
|
||||||
sqlQueryEditorContent: newContent,
|
sqlQueryEditorContent: newContent,
|
||||||
queryCopilotGeneratedQuery: "",
|
queryCopilotGeneratedQuery: "",
|
||||||
|
|
||||||
// Clear the markers when the user edits the document.
|
// Clear the markers when the user edits the document.
|
||||||
modelMarkers: [],
|
modelMarkers: [],
|
||||||
});
|
},
|
||||||
|
() => this.saveQueryTabStateDebounced(),
|
||||||
|
);
|
||||||
if (this.isPreferredApiMongoDB) {
|
if (this.isPreferredApiMongoDB) {
|
||||||
if (newContent.length > 0) {
|
if (newContent.length > 0) {
|
||||||
this.executeQueryButton = {
|
this.executeQueryButton = {
|
||||||
@ -706,8 +659,6 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
this.saveQueryButton.enabled = newContent.length > 0;
|
this.saveQueryButton.enabled = newContent.length > 0;
|
||||||
|
|
||||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||||
|
|
||||||
this.saveQueryTextDebounced(newContent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSelectedContent(selectedContent: string, selection: monaco.Selection): void {
|
public onSelectedContent(selectedContent: string, selection: monaco.Selection): void {
|
||||||
@ -775,28 +726,7 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
document.removeEventListener("keydown", this.handleCopilotKeyDown);
|
document.removeEventListener("keydown", this.handleCopilotKeyDown);
|
||||||
|
|
||||||
// Remove persistence
|
// Remove persistence
|
||||||
const queryTextsIndex = QueryTabComponentImpl.openTabIndexRetriever.getOpenTabIndex(
|
deleteQueryTabState(this.props.tabIndex);
|
||||||
this.props.collection.databaseId,
|
|
||||||
this.props.collection.id(),
|
|
||||||
this.props.tabId,
|
|
||||||
);
|
|
||||||
const queryTexts = readQueryTabSubComponentState<QueryTexts>(SubComponentName.QueryText, this.props.collection, []);
|
|
||||||
|
|
||||||
if (queryTexts.length === 0 || queryTextsIndex >= queryTexts.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
queryTexts.splice(queryTextsIndex, 1);
|
|
||||||
QueryTabComponentImpl.openTabIndexRetriever.removeOpenTabIndex(
|
|
||||||
this.props.collection.databaseId,
|
|
||||||
this.props.collection.id(),
|
|
||||||
this.props.tabId,
|
|
||||||
);
|
|
||||||
if (queryTexts.length === 0) {
|
|
||||||
deleteQueryTabSubComponentState(SubComponentName.QueryText, this.props.collection);
|
|
||||||
} else {
|
|
||||||
saveQueryTabSubComponentState<QueryTexts>(SubComponentName.QueryText, this.props.collection, queryTexts);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getEditorAndQueryResult(): JSX.Element {
|
private getEditorAndQueryResult(): JSX.Element {
|
||||||
@ -818,13 +748,7 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
vertical={vertical}
|
vertical={vertical}
|
||||||
onDragEnd={(sizes: number[]) => {
|
onDragEnd={(sizes: number[]) => {
|
||||||
const queryViewSizePercent = (100 * sizes[0]) / (sizes[0] + sizes[1]);
|
const queryViewSizePercent = (100 * sizes[0]) / (sizes[0] + sizes[1]);
|
||||||
saveQueryTabSubComponentState<number>(
|
this.setState({ queryViewSizePercent }, () => this.saveQueryTabStateDebounced());
|
||||||
SubComponentName.QueryViewSizePercent,
|
|
||||||
this.props.collection,
|
|
||||||
queryViewSizePercent,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
this.setState({ queryViewSizePercent });
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Allotment.Pane data-test="QueryTab/EditorPane" preferredSize={`${this.state.queryViewSizePercent}%`}>
|
<Allotment.Pane data-test="QueryTab/EditorPane" preferredSize={`${this.state.queryViewSizePercent}%`}>
|
||||||
|
@ -1,86 +1,65 @@
|
|||||||
// Definitions of State data
|
// Definitions of State data
|
||||||
|
|
||||||
|
import { ActionType, OpenQueryTab, TabKind } from "Contracts/ActionContracts";
|
||||||
import {
|
import {
|
||||||
AppStateComponentNames,
|
AppStateComponentNames,
|
||||||
deleteSubComponentState,
|
|
||||||
readSubComponentState,
|
readSubComponentState,
|
||||||
saveSubComponentState,
|
saveSubComponentState,
|
||||||
} from "Shared/AppStatePersistenceUtility";
|
} from "Shared/AppStatePersistenceUtility";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
|
|
||||||
export enum SubComponentName {
|
export const OPEN_TABS_SUBCOMPONENT_NAME = "OpenTabs";
|
||||||
SplitterDirection = "SplitterDirection",
|
|
||||||
QueryViewSizePercent = "QueryViewSizePercent",
|
|
||||||
QueryText = "QueryText",
|
|
||||||
}
|
|
||||||
|
|
||||||
export type QueryViewSizePercent = number;
|
export const saveQueryTabState = (
|
||||||
export type QueryTexts = string[];
|
|
||||||
|
|
||||||
// Wrap the ...SubComponentState functions for type safety
|
|
||||||
export const readQueryTabSubComponentState = <T>(
|
|
||||||
subComponentName: SubComponentName,
|
|
||||||
collection: ViewModels.CollectionBase,
|
collection: ViewModels.CollectionBase,
|
||||||
defaultValue: T,
|
state: {
|
||||||
): T => readSubComponentState<T>(AppStateComponentNames.QueryTab, subComponentName, collection, defaultValue);
|
queryText: string;
|
||||||
|
splitterDirection: "vertical" | "horizontal";
|
||||||
|
queryViewSizePercent: number;
|
||||||
|
},
|
||||||
|
tabIndex: number,
|
||||||
|
): void => {
|
||||||
|
const openTabsState = readSubComponentState<OpenQueryTab[]>(
|
||||||
|
AppStateComponentNames.DataExplorerAction,
|
||||||
|
OPEN_TABS_SUBCOMPONENT_NAME,
|
||||||
|
undefined,
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
export const saveQueryTabSubComponentState = <T>(
|
openTabsState[tabIndex] = {
|
||||||
subComponentName: SubComponentName,
|
actionType: ActionType.OpenCollectionTab,
|
||||||
collection: ViewModels.CollectionBase,
|
tabKind: TabKind.SQLQuery,
|
||||||
state: T,
|
databaseResourceId: collection.databaseId,
|
||||||
debounce?: boolean,
|
collectionResourceId: collection.id(),
|
||||||
): void => saveSubComponentState<T>(AppStateComponentNames.QueryTab, subComponentName, collection, state, debounce);
|
query: {
|
||||||
|
text: state.queryText,
|
||||||
|
},
|
||||||
|
splitterDirection: state.splitterDirection,
|
||||||
|
queryViewSizePercent: state.queryViewSizePercent,
|
||||||
|
};
|
||||||
|
|
||||||
export const deleteQueryTabSubComponentState = (
|
saveSubComponentState<OpenQueryTab[]>(
|
||||||
subComponentName: SubComponentName,
|
AppStateComponentNames.DataExplorerAction,
|
||||||
collection: ViewModels.CollectionBase,
|
OPEN_TABS_SUBCOMPONENT_NAME,
|
||||||
) => deleteSubComponentState(AppStateComponentNames.QueryTab, subComponentName, collection);
|
undefined,
|
||||||
|
openTabsState,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
export const deleteQueryTabState = (tabIndex: number): void => {
|
||||||
* For a given databaseId-collectionId tuple:
|
const openTabsState = readSubComponentState<OpenQueryTab[]>(
|
||||||
* Query tab texts are persisted in a form of an array of strings.
|
AppStateComponentNames.DataExplorerAction,
|
||||||
* Each tab's index in the array is determined by the order they are open.
|
OPEN_TABS_SUBCOMPONENT_NAME,
|
||||||
* If a tab is closed, the array is updated to reflect the new order.
|
undefined,
|
||||||
*
|
[],
|
||||||
* We use a map to separate the arrays per databaseId-collectionId tuple.
|
);
|
||||||
* We use a Set for the array to ensure uniqueness of tabId (the set also maintains order of insertion).
|
|
||||||
*/
|
|
||||||
export class OpenTabIndexRetriever {
|
|
||||||
private openTabsMap: Map<string, Set<string>>;
|
|
||||||
|
|
||||||
constructor() {
|
openTabsState.splice(tabIndex, 1);
|
||||||
this.openTabsMap = new Map<string, Set<string>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public getOpenTabIndex(databaseId: string, collectionId: string, tabId: string): number {
|
saveSubComponentState<OpenQueryTab[]>(
|
||||||
const key = `${databaseId}-${collectionId}`;
|
AppStateComponentNames.DataExplorerAction,
|
||||||
const openTabs = this.openTabsMap.get(key);
|
OPEN_TABS_SUBCOMPONENT_NAME,
|
||||||
if (!openTabs) {
|
undefined,
|
||||||
return -1;
|
openTabsState,
|
||||||
}
|
);
|
||||||
|
};
|
||||||
const openTabArray = Array.from(openTabs);
|
|
||||||
return openTabArray.indexOf(tabId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public setOpenTabIndex(databaseId: string, collectionId: string, tabId: string): void {
|
|
||||||
const key = `${databaseId}-${collectionId}`;
|
|
||||||
let openTabs = this.openTabsMap.get(key);
|
|
||||||
if (!openTabs) {
|
|
||||||
openTabs = new Set<string>();
|
|
||||||
this.openTabsMap.set(key, openTabs);
|
|
||||||
}
|
|
||||||
|
|
||||||
openTabs.add(tabId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public removeOpenTabIndex(databaseId: string, collectionId: string, tabId: string): void {
|
|
||||||
const key = `${databaseId}-${collectionId}`;
|
|
||||||
const openTabs = this.openTabsMap.get(key);
|
|
||||||
if (!openTabs) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
openTabs.delete(tabId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -626,7 +626,13 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
public onNewQueryClick(
|
||||||
|
source: any,
|
||||||
|
event: MouseEvent,
|
||||||
|
queryText?: string,
|
||||||
|
stringsplitterDirection?: "horizontal" | "vertical",
|
||||||
|
queryViewSizePercent?: number,
|
||||||
|
) {
|
||||||
const collection: ViewModels.Collection = source.collection || source;
|
const collection: ViewModels.Collection = source.collection || source;
|
||||||
const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
||||||
const title = "Query " + id;
|
const title = "Query " + id;
|
||||||
@ -649,6 +655,8 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
queryText: queryText,
|
queryText: queryText,
|
||||||
partitionKey: collection.partitionKey,
|
partitionKey: collection.partitionKey,
|
||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
|
stringsplitterDirection,
|
||||||
|
queryViewSizePercent,
|
||||||
},
|
},
|
||||||
{ container: this.container },
|
{ container: this.container },
|
||||||
),
|
),
|
||||||
|
@ -9,7 +9,7 @@ export enum AppStateComponentNames {
|
|||||||
DocumentsTab = "DocumentsTab",
|
DocumentsTab = "DocumentsTab",
|
||||||
MostRecentActivity = "MostRecentActivity",
|
MostRecentActivity = "MostRecentActivity",
|
||||||
QueryCopilot = "QueryCopilot",
|
QueryCopilot = "QueryCopilot",
|
||||||
QueryTab = "QueryTab",
|
DataExplorerAction = "DataExplorerAction",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PATH_SEPARATOR = "/"; // export for testing purposes
|
export const PATH_SEPARATOR = "/"; // export for testing purposes
|
||||||
@ -136,7 +136,7 @@ export const deleteAllStates = (): void => {
|
|||||||
export const readSubComponentState = <T>(
|
export const readSubComponentState = <T>(
|
||||||
componentName: AppStateComponentNames,
|
componentName: AppStateComponentNames,
|
||||||
subComponentName: string,
|
subComponentName: string,
|
||||||
collection: ViewModels.CollectionBase,
|
collection: ViewModels.CollectionBase | undefined,
|
||||||
defaultValue: T,
|
defaultValue: T,
|
||||||
): T => {
|
): T => {
|
||||||
const globalAccountName = userContext.databaseAccount?.name;
|
const globalAccountName = userContext.databaseAccount?.name;
|
||||||
@ -151,8 +151,8 @@ export const readSubComponentState = <T>(
|
|||||||
componentName: componentName,
|
componentName: componentName,
|
||||||
subComponentName,
|
subComponentName,
|
||||||
globalAccountName,
|
globalAccountName,
|
||||||
databaseName: collection.databaseId,
|
databaseName: collection ? collection.databaseId : "",
|
||||||
containerName: collection.id(),
|
containerName: collection ? collection.id() : "",
|
||||||
}) as T;
|
}) as T;
|
||||||
|
|
||||||
return state || defaultValue;
|
return state || defaultValue;
|
||||||
@ -168,7 +168,7 @@ export const readSubComponentState = <T>(
|
|||||||
export const saveSubComponentState = <T>(
|
export const saveSubComponentState = <T>(
|
||||||
componentName: AppStateComponentNames,
|
componentName: AppStateComponentNames,
|
||||||
subComponentName: string,
|
subComponentName: string,
|
||||||
collection: ViewModels.CollectionBase,
|
collection: ViewModels.CollectionBase | undefined,
|
||||||
state: T,
|
state: T,
|
||||||
debounce?: boolean,
|
debounce?: boolean,
|
||||||
): void => {
|
): void => {
|
||||||
@ -185,8 +185,8 @@ export const saveSubComponentState = <T>(
|
|||||||
componentName: componentName,
|
componentName: componentName,
|
||||||
subComponentName,
|
subComponentName,
|
||||||
globalAccountName,
|
globalAccountName,
|
||||||
databaseName: collection.databaseId,
|
databaseName: collection ? collection.databaseId : "",
|
||||||
containerName: collection.id(),
|
containerName: collection ? collection.id() : "",
|
||||||
},
|
},
|
||||||
state,
|
state,
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user