mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-24 22:46:40 +00:00
Move tabs manager to zustand (#915)
This commit is contained in:
parent
f4eef1b61b
commit
f8ab0a82e0
@ -50,7 +50,10 @@ export const createDatabaseContextMenu = (container: Explorer, databaseId: strin
|
|||||||
onClick: () =>
|
onClick: () =>
|
||||||
useSidePanel
|
useSidePanel
|
||||||
.getState()
|
.getState()
|
||||||
.openSidePanel("Delete " + getDatabaseName(), <DeleteDatabaseConfirmationPanel explorer={container} />),
|
.openSidePanel(
|
||||||
|
"Delete " + getDatabaseName(),
|
||||||
|
<DeleteDatabaseConfirmationPanel refreshDatabases={() => container.refreshAllDatabases()} />
|
||||||
|
),
|
||||||
label: `Delete ${getDatabaseName()}`,
|
label: `Delete ${getDatabaseName()}`,
|
||||||
styleClass: "deleteDatabaseMenuItem",
|
styleClass: "deleteDatabaseMenuItem",
|
||||||
});
|
});
|
||||||
@ -126,7 +129,10 @@ export const createCollectionContextMenuButton = (
|
|||||||
onClick: () =>
|
onClick: () =>
|
||||||
useSidePanel
|
useSidePanel
|
||||||
.getState()
|
.getState()
|
||||||
.openSidePanel("Delete " + getCollectionName(), <DeleteCollectionConfirmationPane explorer={container} />),
|
.openSidePanel(
|
||||||
|
"Delete " + getCollectionName(),
|
||||||
|
<DeleteCollectionConfirmationPane refreshDatabases={() => container.refreshAllDatabases()} />
|
||||||
|
),
|
||||||
label: `Delete ${getCollectionName()}`,
|
label: `Delete ${getCollectionName()}`,
|
||||||
styleClass: "deleteCollectionMenuItem",
|
styleClass: "deleteCollectionMenuItem",
|
||||||
});
|
});
|
||||||
|
@ -31,7 +31,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"_isInitializingNotebooks": false,
|
"_isInitializingNotebooks": false,
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
"isSchemaEnabled": [Function],
|
|
||||||
"isTabsContentExpanded": [Function],
|
"isTabsContentExpanded": [Function],
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
"onRefreshDatabasesKeyPress": [Function],
|
||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
@ -49,10 +48,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"parameters": [Function],
|
"parameters": [Function],
|
||||||
},
|
},
|
||||||
"tabsManager": TabsManager {
|
|
||||||
"activeTab": [Function],
|
|
||||||
"openedTabs": [Function],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"databaseId": "test",
|
"databaseId": "test",
|
||||||
"defaultTtl": [Function],
|
"defaultTtl": [Function],
|
||||||
@ -107,7 +102,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"_isInitializingNotebooks": false,
|
"_isInitializingNotebooks": false,
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
"isSchemaEnabled": [Function],
|
|
||||||
"isTabsContentExpanded": [Function],
|
"isTabsContentExpanded": [Function],
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
"onRefreshDatabasesKeyPress": [Function],
|
||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
@ -125,10 +119,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"parameters": [Function],
|
"parameters": [Function],
|
||||||
},
|
},
|
||||||
"tabsManager": TabsManager {
|
|
||||||
"activeTab": [Function],
|
|
||||||
"openedTabs": [Function],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"databaseId": "test",
|
"databaseId": "test",
|
||||||
"defaultTtl": [Function],
|
"defaultTtl": [Function],
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { IChoiceGroupProps } from "@fluentui/react";
|
import { IChoiceGroupProps } from "@fluentui/react";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import Q from "q";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import _ from "underscore";
|
import _ from "underscore";
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
@ -17,6 +16,7 @@ import * as DataModels from "../Contracts/DataModels";
|
|||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
|
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
|
||||||
import { useSidePanel } from "../hooks/useSidePanel";
|
import { useSidePanel } from "../hooks/useSidePanel";
|
||||||
|
import { useTabs } from "../hooks/useTabs";
|
||||||
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
|
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
|
||||||
import { ExplorerSettings } from "../Shared/ExplorerSettings";
|
import { ExplorerSettings } from "../Shared/ExplorerSettings";
|
||||||
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
||||||
@ -59,7 +59,7 @@ import { UploadFilePane } from "./Panes/UploadFilePane/UploadFilePane";
|
|||||||
import { UploadItemsPane } from "./Panes/UploadItemsPane/UploadItemsPane";
|
import { UploadItemsPane } from "./Panes/UploadItemsPane/UploadItemsPane";
|
||||||
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
|
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
|
||||||
import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab";
|
import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab";
|
||||||
import { TabsManager } from "./Tabs/TabsManager";
|
import TabsBase from "./Tabs/TabsBase";
|
||||||
import TerminalTab from "./Tabs/TerminalTab";
|
import TerminalTab from "./Tabs/TerminalTab";
|
||||||
import Database from "./Tree/Database";
|
import Database from "./Tree/Database";
|
||||||
import ResourceTokenCollection from "./Tree/ResourceTokenCollection";
|
import ResourceTokenCollection from "./Tree/ResourceTokenCollection";
|
||||||
@ -73,10 +73,6 @@ BindingHandlersRegisterer.registerBindingHandlers();
|
|||||||
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
|
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
|
||||||
var tmp = ComponentRegisterer;
|
var tmp = ComponentRegisterer;
|
||||||
|
|
||||||
export interface ExplorerParams {
|
|
||||||
tabsManager: TabsManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Explorer {
|
export default class Explorer {
|
||||||
public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>;
|
public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>;
|
||||||
public queriesClient: QueriesClient;
|
public queriesClient: QueriesClient;
|
||||||
@ -90,10 +86,8 @@ export default class Explorer {
|
|||||||
|
|
||||||
// Tabs
|
// Tabs
|
||||||
public isTabsContentExpanded: ko.Observable<boolean>;
|
public isTabsContentExpanded: ko.Observable<boolean>;
|
||||||
public tabsManager: TabsManager;
|
|
||||||
|
|
||||||
public gitHubOAuthService: GitHubOAuthService;
|
public gitHubOAuthService: GitHubOAuthService;
|
||||||
public isSchemaEnabled: ko.Computed<boolean>;
|
|
||||||
|
|
||||||
// Notebooks
|
// Notebooks
|
||||||
public notebookManager?: NotebookManager;
|
public notebookManager?: NotebookManager;
|
||||||
@ -106,7 +100,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
private static readonly MaxNbDatabasesToAutoExpand = 5;
|
private static readonly MaxNbDatabasesToAutoExpand = 5;
|
||||||
|
|
||||||
constructor(params?: ExplorerParams) {
|
constructor() {
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, {
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
@ -117,7 +111,6 @@ export default class Explorer {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.queriesClient = new QueriesClient(this);
|
this.queriesClient = new QueriesClient(this);
|
||||||
this.isSchemaEnabled = ko.computed<boolean>(() => userContext.features.enableSchema);
|
|
||||||
|
|
||||||
useSelectedNode.subscribe(() => {
|
useSelectedNode.subscribe(() => {
|
||||||
// Make sure switching tabs restores tabs display
|
// Make sure switching tabs restores tabs display
|
||||||
@ -136,13 +129,15 @@ export default class Explorer {
|
|||||||
return isCapabilityEnabled("EnableMongo");
|
return isCapabilityEnabled("EnableMongo");
|
||||||
});
|
});
|
||||||
|
|
||||||
this.tabsManager = params?.tabsManager ?? new TabsManager();
|
useTabs.subscribe(
|
||||||
this.tabsManager.openedTabs.subscribe((tabs) => {
|
(openedTabs: TabsBase[]) => {
|
||||||
if (tabs.length === 0) {
|
if (openedTabs.length === 0) {
|
||||||
useSelectedNode.getState().setSelectedNode(undefined);
|
useSelectedNode.getState().setSelectedNode(undefined);
|
||||||
useCommandBar.getState().setContextButtons([]);
|
useCommandBar.getState().setContextButtons([]);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
(state) => state.openedTabs
|
||||||
|
);
|
||||||
|
|
||||||
this.isTabsContentExpanded = ko.observable(false);
|
this.isTabsContentExpanded = ko.observable(false);
|
||||||
|
|
||||||
@ -283,35 +278,26 @@ export default class Explorer {
|
|||||||
// TODO: return result
|
// TODO: return result
|
||||||
}
|
}
|
||||||
|
|
||||||
public refreshDatabaseForResourceToken(): Promise<void> {
|
public async refreshDatabaseForResourceToken(): Promise<void> {
|
||||||
const databaseId = userContext.parsedResourceToken?.databaseId;
|
const databaseId = userContext.parsedResourceToken?.databaseId;
|
||||||
const collectionId = userContext.parsedResourceToken?.collectionId;
|
const collectionId = userContext.parsedResourceToken?.collectionId;
|
||||||
if (!databaseId || !collectionId) {
|
if (!databaseId || !collectionId) {
|
||||||
return Promise.reject();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return readCollection(databaseId, collectionId).then((collection: DataModels.Collection) => {
|
const collection: DataModels.Collection = await readCollection(databaseId, collectionId);
|
||||||
const resourceTokenCollection = new ResourceTokenCollection(this, databaseId, collection);
|
const resourceTokenCollection = new ResourceTokenCollection(this, databaseId, collection);
|
||||||
useDatabases.setState({ resourceTokenCollection });
|
useDatabases.setState({ resourceTokenCollection });
|
||||||
useSelectedNode.getState().setSelectedNode(resourceTokenCollection);
|
useSelectedNode.getState().setSelectedNode(resourceTokenCollection);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public refreshAllDatabases(isInitialLoad?: boolean): Q.Promise<any> {
|
public async refreshAllDatabases(): Promise<void> {
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.LoadDatabases, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.LoadDatabases, {
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
let resourceTreeStartKey: number = null;
|
|
||||||
if (isInitialLoad) {
|
|
||||||
resourceTreeStartKey = TelemetryProcessor.traceStart(Action.LoadResourceTree, {
|
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Refactor
|
try {
|
||||||
const deferred: Q.Deferred<any> = Q.defer();
|
const databases: DataModels.Database[] = await readDatabases();
|
||||||
readDatabases().then(
|
|
||||||
(databases: DataModels.Database[]) => {
|
|
||||||
TelemetryProcessor.traceSuccess(
|
TelemetryProcessor.traceSuccess(
|
||||||
Action.LoadDatabases,
|
Action.LoadDatabases,
|
||||||
{
|
{
|
||||||
@ -319,20 +305,17 @@ export default class Explorer {
|
|||||||
},
|
},
|
||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
const deltaDatabases = this.getDeltaDatabases(databases);
|
const currentDatabases = useDatabases.getState().databases;
|
||||||
this.addDatabasesToList(deltaDatabases.toAdd);
|
const deltaDatabases = this.getDeltaDatabases(databases, currentDatabases);
|
||||||
this.deleteDatabasesFromList(deltaDatabases.toDelete);
|
let updatedDatabases = currentDatabases.filter(
|
||||||
this.refreshAndExpandNewDatabases(deltaDatabases.toAdd).then(
|
(database) => !deltaDatabases.toDelete.some((deletedDatabase) => deletedDatabase.id() === database.id())
|
||||||
() => {
|
|
||||||
deferred.resolve();
|
|
||||||
},
|
|
||||||
(reason) => {
|
|
||||||
deferred.reject(reason);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
},
|
updatedDatabases = [...updatedDatabases, ...deltaDatabases.toAdd].sort((db1, db2) =>
|
||||||
(error) => {
|
db1.id().localeCompare(db2.id())
|
||||||
deferred.reject(error);
|
);
|
||||||
|
useDatabases.setState({ databases: updatedDatabases });
|
||||||
|
await this.refreshAndExpandNewDatabases(deltaDatabases.toAdd, currentDatabases);
|
||||||
|
} catch (error) {
|
||||||
const errorMessage = getErrorMessage(error);
|
const errorMessage = getErrorMessage(error);
|
||||||
TelemetryProcessor.traceFailure(
|
TelemetryProcessor.traceFailure(
|
||||||
Action.LoadDatabases,
|
Action.LoadDatabases,
|
||||||
@ -345,34 +328,6 @@ export default class Explorer {
|
|||||||
);
|
);
|
||||||
logConsoleError(`Error while refreshing databases: ${errorMessage}`);
|
logConsoleError(`Error while refreshing databases: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
return deferred.promise.then(
|
|
||||||
() => {
|
|
||||||
if (resourceTreeStartKey != null) {
|
|
||||||
TelemetryProcessor.traceSuccess(
|
|
||||||
Action.LoadResourceTree,
|
|
||||||
{
|
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
|
||||||
},
|
|
||||||
resourceTreeStartKey
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
if (resourceTreeStartKey != null) {
|
|
||||||
TelemetryProcessor.traceFailure(
|
|
||||||
Action.LoadResourceTree,
|
|
||||||
{
|
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
|
||||||
error: getErrorMessage(error),
|
|
||||||
errorStack: getErrorStack(error),
|
|
||||||
},
|
|
||||||
resourceTreeStartKey
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onRefreshDatabasesKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
public onRefreshDatabasesKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||||
@ -513,69 +468,13 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private refreshAndExpandNewDatabases(newDatabases: ViewModels.Database[]): Q.Promise<void> {
|
|
||||||
// we reload collections for all databases so the resource tree reflects any collection-level changes
|
|
||||||
// i.e addition of stored procedures, etc.
|
|
||||||
const deferred: Q.Deferred<void> = Q.defer<void>();
|
|
||||||
let loadCollectionPromises: Q.Promise<void>[] = [];
|
|
||||||
|
|
||||||
// If the user has a lot of databases, only load expanded databases.
|
|
||||||
const databases = useDatabases.getState().databases;
|
|
||||||
const databasesToLoad =
|
|
||||||
databases.length <= Explorer.MaxNbDatabasesToAutoExpand
|
|
||||||
? databases
|
|
||||||
: databases.filter((db) => db.isDatabaseExpanded() || db.id() === Constants.SavedQueries.DatabaseName);
|
|
||||||
|
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.LoadCollections, {
|
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
|
||||||
});
|
|
||||||
databasesToLoad.forEach(async (database: ViewModels.Database) => {
|
|
||||||
await database.loadCollections();
|
|
||||||
const isNewDatabase: boolean = _.some(newDatabases, (db: ViewModels.Database) => db.id() === database.id());
|
|
||||||
if (isNewDatabase) {
|
|
||||||
database.expandDatabase();
|
|
||||||
}
|
|
||||||
this.tabsManager.refreshActiveTab((tab) => tab.collection && tab.collection.getDatabase().id() === database.id());
|
|
||||||
});
|
|
||||||
|
|
||||||
Q.all(loadCollectionPromises).done(
|
|
||||||
() => {
|
|
||||||
deferred.resolve();
|
|
||||||
TelemetryProcessor.traceSuccess(
|
|
||||||
Action.LoadCollections,
|
|
||||||
{ dataExplorerArea: Constants.Areas.ResourceTree },
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
deferred.reject(error);
|
|
||||||
TelemetryProcessor.traceFailure(
|
|
||||||
Action.LoadCollections,
|
|
||||||
{
|
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
|
||||||
error: getErrorMessage(error),
|
|
||||||
errorStack: getErrorStack(error),
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _initSettings() {
|
|
||||||
if (!ExplorerSettings.hasSettingsDefined()) {
|
|
||||||
ExplorerSettings.createDefaultSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getDeltaDatabases(
|
private getDeltaDatabases(
|
||||||
updatedDatabaseList: DataModels.Database[]
|
updatedDatabaseList: DataModels.Database[],
|
||||||
|
databases: ViewModels.Database[]
|
||||||
): {
|
): {
|
||||||
toAdd: ViewModels.Database[];
|
toAdd: ViewModels.Database[];
|
||||||
toDelete: ViewModels.Database[];
|
toDelete: ViewModels.Database[];
|
||||||
} {
|
} {
|
||||||
const databases = useDatabases.getState().databases;
|
|
||||||
const newDatabases: DataModels.Database[] = _.filter(updatedDatabaseList, (database: DataModels.Database) => {
|
const newDatabases: DataModels.Database[] = _.filter(updatedDatabaseList, (database: DataModels.Database) => {
|
||||||
const databaseExists = _.some(
|
const databaseExists = _.some(
|
||||||
databases,
|
databases,
|
||||||
@ -587,8 +486,8 @@ export default class Explorer {
|
|||||||
(newDatabase: DataModels.Database) => new Database(this, newDatabase)
|
(newDatabase: DataModels.Database) => new Database(this, newDatabase)
|
||||||
);
|
);
|
||||||
|
|
||||||
let databasesToDelete: ViewModels.Database[] = [];
|
const databasesToDelete: ViewModels.Database[] = [];
|
||||||
ko.utils.arrayForEach(databases, (database: ViewModels.Database) => {
|
databases.forEach((database: ViewModels.Database) => {
|
||||||
const databasePresentInUpdatedList = _.some(
|
const databasePresentInUpdatedList = _.some(
|
||||||
updatedDatabaseList,
|
updatedDatabaseList,
|
||||||
(db: DataModels.Database) => db.id === database.id()
|
(db: DataModels.Database) => db.id === database.id()
|
||||||
@ -601,13 +500,58 @@ export default class Explorer {
|
|||||||
return { toAdd: databasesToAdd, toDelete: databasesToDelete };
|
return { toAdd: databasesToAdd, toDelete: databasesToDelete };
|
||||||
}
|
}
|
||||||
|
|
||||||
private addDatabasesToList(databases: ViewModels.Database[]): void {
|
private async refreshAndExpandNewDatabases(
|
||||||
useDatabases.getState().addDatabases(databases);
|
newDatabases: ViewModels.Database[],
|
||||||
|
databases: ViewModels.Database[]
|
||||||
|
): Promise<void> {
|
||||||
|
// we reload collections for all databases so the resource tree reflects any collection-level changes
|
||||||
|
// i.e addition of stored procedures, etc.
|
||||||
|
|
||||||
|
// If the user has a lot of databases, only load expanded databases.
|
||||||
|
const databasesToLoad =
|
||||||
|
databases.length <= Explorer.MaxNbDatabasesToAutoExpand
|
||||||
|
? databases
|
||||||
|
: databases.filter((db) => db.isDatabaseExpanded() || db.id() === Constants.SavedQueries.DatabaseName);
|
||||||
|
|
||||||
|
const startKey: number = TelemetryProcessor.traceStart(Action.LoadCollections, {
|
||||||
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Promise.all(
|
||||||
|
databasesToLoad.map(async (database: ViewModels.Database) => {
|
||||||
|
await database.loadCollections();
|
||||||
|
const isNewDatabase: boolean = _.some(newDatabases, (db: ViewModels.Database) => db.id() === database.id());
|
||||||
|
if (isNewDatabase) {
|
||||||
|
database.expandDatabase();
|
||||||
|
}
|
||||||
|
useTabs
|
||||||
|
.getState()
|
||||||
|
.refreshActiveTab((tab) => tab.collection && tab.collection.getDatabase().id() === database.id());
|
||||||
|
TelemetryProcessor.traceSuccess(
|
||||||
|
Action.LoadCollections,
|
||||||
|
{ dataExplorerArea: Constants.Areas.ResourceTree },
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
TelemetryProcessor.traceFailure(
|
||||||
|
Action.LoadCollections,
|
||||||
|
{
|
||||||
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
|
error: getErrorMessage(error),
|
||||||
|
errorStack: getErrorStack(error),
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private deleteDatabasesFromList(databasesToRemove: ViewModels.Database[]): void {
|
private _initSettings() {
|
||||||
const deleteDatabase = useDatabases.getState().deleteDatabase;
|
if (!ExplorerSettings.hasSettingsDefined()) {
|
||||||
databasesToRemove.forEach((database) => deleteDatabase(database));
|
ExplorerSettings.createDefaultSettings();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public uploadFile(name: string, content: string, parent: NotebookContentItem): Promise<NotebookContentItem> {
|
public uploadFile(name: string, content: string, parent: NotebookContentItem): Promise<NotebookContentItem> {
|
||||||
@ -750,7 +694,9 @@ export default class Explorer {
|
|||||||
throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`);
|
throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const notebookTabs = this.tabsManager.getTabs(
|
const notebookTabs = useTabs
|
||||||
|
.getState()
|
||||||
|
.getTabs(
|
||||||
ViewModels.CollectionTabKind.NotebookV2,
|
ViewModels.CollectionTabKind.NotebookV2,
|
||||||
(tab) =>
|
(tab) =>
|
||||||
(tab as NotebookV2Tab).notebookPath &&
|
(tab as NotebookV2Tab).notebookPath &&
|
||||||
@ -759,7 +705,7 @@ export default class Explorer {
|
|||||||
let notebookTab = notebookTabs && notebookTabs[0];
|
let notebookTab = notebookTabs && notebookTabs[0];
|
||||||
|
|
||||||
if (notebookTab) {
|
if (notebookTab) {
|
||||||
this.tabsManager.activateTab(notebookTab);
|
useTabs.getState().activateTab(notebookTab);
|
||||||
} else {
|
} else {
|
||||||
const options: NotebookTabOptions = {
|
const options: NotebookTabOptions = {
|
||||||
account: userContext.databaseAccount,
|
account: userContext.databaseAccount,
|
||||||
@ -778,7 +724,7 @@ export default class Explorer {
|
|||||||
try {
|
try {
|
||||||
const NotebookTabV2 = await import(/* webpackChunkName: "NotebookV2Tab" */ "./Tabs/NotebookV2Tab");
|
const NotebookTabV2 = await import(/* webpackChunkName: "NotebookV2Tab" */ "./Tabs/NotebookV2Tab");
|
||||||
notebookTab = new NotebookTabV2.default(options);
|
notebookTab = new NotebookTabV2.default(options);
|
||||||
this.tabsManager.activateNewTab(notebookTab);
|
useTabs.getState().activateNewTab(notebookTab);
|
||||||
} catch (reason) {
|
} catch (reason) {
|
||||||
console.error("Import NotebookV2Tab failed!", reason);
|
console.error("Import NotebookV2Tab failed!", reason);
|
||||||
return false;
|
return false;
|
||||||
@ -796,19 +742,17 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Don't delete if tab is open to avoid accidental deletion
|
// Don't delete if tab is open to avoid accidental deletion
|
||||||
const openedNotebookTabs = this.tabsManager.getTabs(
|
const openedNotebookTabs = useTabs
|
||||||
ViewModels.CollectionTabKind.NotebookV2,
|
.getState()
|
||||||
(tab: NotebookV2Tab) => {
|
.getTabs(ViewModels.CollectionTabKind.NotebookV2, (tab: NotebookV2Tab) => {
|
||||||
return tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), notebookFile.path);
|
return tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), notebookFile.path);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
if (openedNotebookTabs.length > 0) {
|
if (openedNotebookTabs.length > 0) {
|
||||||
this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again.");
|
this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again.");
|
||||||
} else {
|
} else {
|
||||||
useSidePanel.getState().openSidePanel(
|
useSidePanel.getState().openSidePanel(
|
||||||
"Rename Notebook",
|
"Rename Notebook",
|
||||||
<StringInputPane
|
<StringInputPane
|
||||||
explorer={this}
|
|
||||||
closePanel={() => {
|
closePanel={() => {
|
||||||
useSidePanel.getState().closeSidePanel();
|
useSidePanel.getState().closeSidePanel();
|
||||||
this.resourceTree.triggerRender();
|
this.resourceTree.triggerRender();
|
||||||
@ -839,7 +783,6 @@ export default class Explorer {
|
|||||||
useSidePanel.getState().openSidePanel(
|
useSidePanel.getState().openSidePanel(
|
||||||
"Create new directory",
|
"Create new directory",
|
||||||
<StringInputPane
|
<StringInputPane
|
||||||
explorer={this}
|
|
||||||
closePanel={() => {
|
closePanel={() => {
|
||||||
useSidePanel.getState().closeSidePanel();
|
useSidePanel.getState().closeSidePanel();
|
||||||
this.resourceTree.triggerRender();
|
this.resourceTree.triggerRender();
|
||||||
@ -927,12 +870,11 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Don't delete if tab is open to avoid accidental deletion
|
// Don't delete if tab is open to avoid accidental deletion
|
||||||
const openedNotebookTabs = this.tabsManager.getTabs(
|
const openedNotebookTabs = useTabs
|
||||||
ViewModels.CollectionTabKind.NotebookV2,
|
.getState()
|
||||||
(tab: NotebookV2Tab) => {
|
.getTabs(ViewModels.CollectionTabKind.NotebookV2, (tab: NotebookV2Tab) => {
|
||||||
return tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), item.path);
|
return tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), item.path);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
if (openedNotebookTabs.length > 0) {
|
if (openedNotebookTabs.length > 0) {
|
||||||
this.showOkModalDialog("Unable to delete file", "This file is being edited. Please close the tab and try again.");
|
this.showOkModalDialog("Unable to delete file", "This file is being edited. Please close the tab and try again.");
|
||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
@ -1034,10 +976,9 @@ export default class Explorer {
|
|||||||
throw new Error("Terminal kind: ${kind} not supported");
|
throw new Error("Terminal kind: ${kind} not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
const terminalTabs: TerminalTab[] = this.tabsManager.getTabs(
|
const terminalTabs: TerminalTab[] = useTabs
|
||||||
ViewModels.CollectionTabKind.Terminal,
|
.getState()
|
||||||
(tab) => tab.tabTitle() === title
|
.getTabs(ViewModels.CollectionTabKind.Terminal, (tab) => tab.tabTitle() === title) as TerminalTab[];
|
||||||
) as TerminalTab[];
|
|
||||||
|
|
||||||
let index = 1;
|
let index = 1;
|
||||||
if (terminalTabs.length > 0) {
|
if (terminalTabs.length > 0) {
|
||||||
@ -1058,7 +999,7 @@ export default class Explorer {
|
|||||||
index: index,
|
index: index,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.tabsManager.activateNewTab(newTab);
|
useTabs.getState().activateNewTab(newTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async openGallery(
|
public async openGallery(
|
||||||
@ -1069,14 +1010,15 @@ export default class Explorer {
|
|||||||
) {
|
) {
|
||||||
const title = "Gallery";
|
const title = "Gallery";
|
||||||
const GalleryTab = await (await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab")).default;
|
const GalleryTab = await (await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab")).default;
|
||||||
const galleryTab = this.tabsManager
|
const galleryTab = useTabs
|
||||||
|
.getState()
|
||||||
.getTabs(ViewModels.CollectionTabKind.Gallery)
|
.getTabs(ViewModels.CollectionTabKind.Gallery)
|
||||||
.find((tab) => tab.tabTitle() == title);
|
.find((tab) => tab.tabTitle() == title);
|
||||||
|
|
||||||
if (galleryTab instanceof GalleryTab) {
|
if (galleryTab instanceof GalleryTab) {
|
||||||
this.tabsManager.activateTab(galleryTab);
|
useTabs.getState().activateTab(galleryTab);
|
||||||
} else {
|
} else {
|
||||||
this.tabsManager.activateNewTab(
|
useTabs.getState().activateNewTab(
|
||||||
new GalleryTab(
|
new GalleryTab(
|
||||||
{
|
{
|
||||||
tabKind: ViewModels.CollectionTabKind.Gallery,
|
tabKind: ViewModels.CollectionTabKind.Gallery,
|
||||||
@ -1116,7 +1058,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private refreshCommandBarButtons(): void {
|
private refreshCommandBarButtons(): void {
|
||||||
const activeTab = this.tabsManager.activeTab();
|
const activeTab = useTabs.getState().activeTab;
|
||||||
if (activeTab) {
|
if (activeTab) {
|
||||||
activeTab.onActivate(); // TODO only update tabs buttons?
|
activeTab.onActivate(); // TODO only update tabs buttons?
|
||||||
} else {
|
} else {
|
||||||
@ -1208,7 +1150,7 @@ export default class Explorer {
|
|||||||
public async refreshExplorer(): Promise<void> {
|
public async refreshExplorer(): Promise<void> {
|
||||||
userContext.authType === AuthType.ResourceToken
|
userContext.authType === AuthType.ResourceToken
|
||||||
? this.refreshDatabaseForResourceToken()
|
? this.refreshDatabaseForResourceToken()
|
||||||
: this.refreshAllDatabases(true);
|
: this.refreshAllDatabases();
|
||||||
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
|
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
|
||||||
const isNotebookEnabled: boolean =
|
const isNotebookEnabled: boolean =
|
||||||
userContext.authType !== AuthType.ResourceToken &&
|
userContext.authType !== AuthType.ResourceToken &&
|
||||||
|
@ -8,6 +8,7 @@ import * as React from "react";
|
|||||||
import create, { UseStore } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { StyleConstants } from "../../../Common/Constants";
|
import { StyleConstants } from "../../../Common/Constants";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { useSelectedNode } from "../../useSelectedNode";
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
@ -53,7 +54,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
|||||||
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
|
||||||
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
|
||||||
|
|
||||||
if (container.tabsManager.activeTab()?.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
|
if (useTabs.getState().activeTab?.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
|
||||||
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));
|
uiFabricControlButtons.unshift(CommandBarUtil.createMemoryTracker("memoryTracker"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ import {
|
|||||||
import { webSocket } from "rxjs/webSocket";
|
import { webSocket } from "rxjs/webSocket";
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import { Areas } from "../../../Common/Constants";
|
import { Areas } from "../../../Common/Constants";
|
||||||
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
import { Action as TelemetryAction, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action as TelemetryAction, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { logConsoleError, logConsoleInfo } from "../../../Utils/NotificationConsoleUtils";
|
import { logConsoleError, logConsoleInfo } from "../../../Utils/NotificationConsoleUtils";
|
||||||
@ -776,7 +777,9 @@ const closeUnsupportedMimetypesEpic = (
|
|||||||
if (explorer && !TextFile.handles(mimetype)) {
|
if (explorer && !TextFile.handles(mimetype)) {
|
||||||
const filepath = action.payload.filepath;
|
const filepath = action.payload.filepath;
|
||||||
// Close tab and show error message
|
// Close tab and show error message
|
||||||
explorer.tabsManager.closeTabsByComparator(
|
useTabs
|
||||||
|
.getState()
|
||||||
|
.closeTabsByComparator(
|
||||||
(tab: any) => (tab as any).notebookPath && FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath)
|
(tab: any) => (tab as any).notebookPath && FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath)
|
||||||
);
|
);
|
||||||
const msg = `${filepath} cannot be rendered. Please download the file, in order to view it outside of Data Explorer.`;
|
const msg = `${filepath} cannot be rendered. Please download the file, in order to view it outside of Data Explorer.`;
|
||||||
@ -804,7 +807,9 @@ const closeContentFailedToFetchEpic = (
|
|||||||
if (explorer) {
|
if (explorer) {
|
||||||
const filepath = action.payload.filepath;
|
const filepath = action.payload.filepath;
|
||||||
// Close tab and show error message
|
// Close tab and show error message
|
||||||
explorer.tabsManager.closeTabsByComparator(
|
useTabs
|
||||||
|
.getState()
|
||||||
|
.closeTabsByComparator(
|
||||||
(tab: any) => (tab as any).notebookPath && FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath)
|
(tab: any) => (tab as any).notebookPath && FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath)
|
||||||
);
|
);
|
||||||
const msg = `Failed to load file: ${filepath}.`;
|
const msg = `Failed to load file: ${filepath}.`;
|
||||||
|
@ -4,6 +4,7 @@ import { logError } from "../../../Common/Logger";
|
|||||||
import { Query } from "../../../Contracts/DataModels";
|
import { Query } from "../../../Contracts/DataModels";
|
||||||
import { Collection } from "../../../Contracts/ViewModels";
|
import { Collection } from "../../../Contracts/ViewModels";
|
||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { trace } from "../../../Shared/Telemetry/TelemetryProcessor";
|
import { trace } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
@ -36,7 +37,7 @@ export const BrowseQueriesPane: FunctionComponent<BrowseQueriesPaneProps> = ({
|
|||||||
selectedCollection.onNewQueryClick(selectedCollection, undefined, savedQuery.query);
|
selectedCollection.onNewQueryClick(selectedCollection, undefined, savedQuery.query);
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryTab = explorer && (explorer.tabsManager.activeTab() as NewQueryTab);
|
const queryTab = useTabs.getState().activeTab as NewQueryTab;
|
||||||
queryTab.tabTitle(savedQuery.queryName);
|
queryTab.tabTitle(savedQuery.queryName);
|
||||||
queryTab.tabPath(`${selectedCollection.databaseId}>${selectedCollection.id()}>${savedQuery.queryName}`);
|
queryTab.tabPath(`${selectedCollection.databaseId}>${selectedCollection.id()}>${savedQuery.queryName}`);
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ import { Collection, Database } from "../../../Contracts/ViewModels";
|
|||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { updateUserContext } from "../../../UserContext";
|
import { updateUserContext } from "../../../UserContext";
|
||||||
import Explorer from "../../Explorer";
|
|
||||||
import { useDatabases } from "../../useDatabases";
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { useSelectedNode } from "../../useSelectedNode";
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
import { DeleteCollectionConfirmationPane } from "./DeleteCollectionConfirmationPane";
|
import { DeleteCollectionConfirmationPane } from "./DeleteCollectionConfirmationPane";
|
||||||
@ -53,10 +52,7 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
|
|
||||||
describe("shouldRecordFeedback()", () => {
|
describe("shouldRecordFeedback()", () => {
|
||||||
it("should return true if last collection and database does not have shared throughput else false", () => {
|
it("should return true if last collection and database does not have shared throughput else false", () => {
|
||||||
const fakeExplorer = new Explorer();
|
const wrapper = shallow(<DeleteCollectionConfirmationPane refreshDatabases={() => undefined} />);
|
||||||
fakeExplorer.refreshAllDatabases = () => undefined;
|
|
||||||
|
|
||||||
const wrapper = shallow(<DeleteCollectionConfirmationPane explorer={fakeExplorer} />);
|
|
||||||
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false);
|
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false);
|
||||||
|
|
||||||
const database = { id: ko.observable("testDB") } as Database;
|
const database = { id: ko.observable("testDB") } as Database;
|
||||||
@ -65,11 +61,11 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
database.isDatabaseShared = ko.computed(() => false);
|
database.isDatabaseShared = ko.computed(() => false);
|
||||||
useDatabases.getState().addDatabases([database]);
|
useDatabases.getState().addDatabases([database]);
|
||||||
useSelectedNode.getState().setSelectedNode(database);
|
useSelectedNode.getState().setSelectedNode(database);
|
||||||
wrapper.setProps({ explorer: fakeExplorer });
|
wrapper.setProps({});
|
||||||
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(true);
|
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(true);
|
||||||
|
|
||||||
database.isDatabaseShared = ko.computed(() => true);
|
database.isDatabaseShared = ko.computed(() => true);
|
||||||
wrapper.setProps({ explorer: fakeExplorer });
|
wrapper.setProps({});
|
||||||
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false);
|
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -77,8 +73,6 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
describe("submit()", () => {
|
describe("submit()", () => {
|
||||||
const selectedCollectionId = "testCol";
|
const selectedCollectionId = "testCol";
|
||||||
const databaseId = "testDatabase";
|
const databaseId = "testDatabase";
|
||||||
const fakeExplorer = {} as Explorer;
|
|
||||||
fakeExplorer.refreshAllDatabases = () => undefined;
|
|
||||||
const database = { id: ko.observable(databaseId) } as Database;
|
const database = { id: ko.observable(databaseId) } as Database;
|
||||||
const collection = {
|
const collection = {
|
||||||
id: ko.observable(selectedCollectionId),
|
id: ko.observable(selectedCollectionId),
|
||||||
@ -115,7 +109,7 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should call delete collection", () => {
|
it("should call delete collection", () => {
|
||||||
const wrapper = mount(<DeleteCollectionConfirmationPane explorer={fakeExplorer} />);
|
const wrapper = mount(<DeleteCollectionConfirmationPane refreshDatabases={() => undefined} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
|
||||||
expect(wrapper.exists("#confirmCollectionId")).toBe(true);
|
expect(wrapper.exists("#confirmCollectionId")).toBe(true);
|
||||||
@ -132,7 +126,7 @@ describe("Delete Collection Confirmation Pane", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should record feedback", async () => {
|
it("should record feedback", async () => {
|
||||||
const wrapper = mount(<DeleteCollectionConfirmationPane explorer={fakeExplorer} />);
|
const wrapper = mount(<DeleteCollectionConfirmationPane refreshDatabases={() => undefined} />);
|
||||||
expect(wrapper.exists("#confirmCollectionId")).toBe(true);
|
expect(wrapper.exists("#confirmCollectionId")).toBe(true);
|
||||||
wrapper
|
wrapper
|
||||||
.find("#confirmCollectionId")
|
.find("#confirmCollectionId")
|
||||||
|
@ -6,23 +6,23 @@ import DeleteFeedback from "../../../Common/DeleteFeedback";
|
|||||||
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||||
import { Collection } from "../../../Contracts/ViewModels";
|
import { Collection } from "../../../Contracts/ViewModels";
|
||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
import { DefaultExperienceUtility } from "../../../Shared/DefaultExperienceUtility";
|
import { DefaultExperienceUtility } from "../../../Shared/DefaultExperienceUtility";
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { getCollectionName } from "../../../Utils/APITypeUtils";
|
import { getCollectionName } from "../../../Utils/APITypeUtils";
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
|
||||||
import { useDatabases } from "../../useDatabases";
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { useSelectedNode } from "../../useSelectedNode";
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
export interface DeleteCollectionConfirmationPaneProps {
|
export interface DeleteCollectionConfirmationPaneProps {
|
||||||
explorer: Explorer;
|
refreshDatabases: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectionConfirmationPaneProps> = ({
|
export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectionConfirmationPaneProps> = ({
|
||||||
explorer,
|
refreshDatabases,
|
||||||
}: DeleteCollectionConfirmationPaneProps) => {
|
}: DeleteCollectionConfirmationPaneProps) => {
|
||||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
const [deleteCollectionFeedback, setDeleteCollectionFeedback] = useState<string>("");
|
const [deleteCollectionFeedback, setDeleteCollectionFeedback] = useState<string>("");
|
||||||
@ -31,8 +31,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
const [isExecuting, setIsExecuting] = useState(false);
|
const [isExecuting, setIsExecuting] = useState(false);
|
||||||
|
|
||||||
const shouldRecordFeedback = (): boolean =>
|
const shouldRecordFeedback = (): boolean =>
|
||||||
useDatabases.getState().isLastCollection() &&
|
useDatabases.getState().isLastCollection() && !useDatabases.getState().findSelectedDatabase()?.isDatabaseShared();
|
||||||
!useSelectedNode.getState().findSelectedDatabase()?.isDatabaseShared();
|
|
||||||
|
|
||||||
const collectionName = getCollectionName().toLocaleLowerCase();
|
const collectionName = getCollectionName().toLocaleLowerCase();
|
||||||
const paneTitle = "Delete " + collectionName;
|
const paneTitle = "Delete " + collectionName;
|
||||||
@ -63,10 +62,12 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
|
|
||||||
setIsExecuting(false);
|
setIsExecuting(false);
|
||||||
useSelectedNode.getState().setSelectedNode(collection.database);
|
useSelectedNode.getState().setSelectedNode(collection.database);
|
||||||
explorer.tabsManager?.closeTabsByComparator(
|
useTabs
|
||||||
|
.getState()
|
||||||
|
.closeTabsByComparator(
|
||||||
(tab) => tab.node?.id() === collection.id() && (tab.node as Collection).databaseId === collection.databaseId
|
(tab) => tab.node?.id() === collection.id() && (tab.node as Collection).databaseId === collection.databaseId
|
||||||
);
|
);
|
||||||
explorer.refreshAllDatabases();
|
refreshDatabases();
|
||||||
|
|
||||||
TelemetryProcessor.traceSuccess(Action.DeleteCollection, paneInfo, startKey);
|
TelemetryProcessor.traceSuccess(Action.DeleteCollection, paneInfo, startKey);
|
||||||
|
|
||||||
|
@ -2,11 +2,7 @@
|
|||||||
|
|
||||||
exports[`Delete Collection Confirmation Pane submit() should call delete collection 1`] = `
|
exports[`Delete Collection Confirmation Pane submit() should call delete collection 1`] = `
|
||||||
<DeleteCollectionConfirmationPane
|
<DeleteCollectionConfirmationPane
|
||||||
explorer={
|
refreshDatabases={[Function]}
|
||||||
Object {
|
|
||||||
"refreshAllDatabases": [Function],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<RightPaneForm
|
<RightPaneForm
|
||||||
formError=""
|
formError=""
|
||||||
|
@ -10,15 +10,12 @@ import { Collection, Database } from "../../Contracts/ViewModels";
|
|||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { updateUserContext } from "../../UserContext";
|
import { updateUserContext } from "../../UserContext";
|
||||||
import Explorer from "../Explorer";
|
|
||||||
import { TabsManager } from "../Tabs/TabsManager";
|
|
||||||
import { useDatabases } from "../useDatabases";
|
import { useDatabases } from "../useDatabases";
|
||||||
import { useSelectedNode } from "../useSelectedNode";
|
import { useSelectedNode } from "../useSelectedNode";
|
||||||
import { DeleteDatabaseConfirmationPanel } from "./DeleteDatabaseConfirmationPanel";
|
import { DeleteDatabaseConfirmationPanel } from "./DeleteDatabaseConfirmationPanel";
|
||||||
|
|
||||||
describe("Delete Database Confirmation Pane", () => {
|
describe("Delete Database Confirmation Pane", () => {
|
||||||
const selectedDatabaseId = "testDatabase";
|
const selectedDatabaseId = "testDatabase";
|
||||||
let fakeExplorer: Explorer;
|
|
||||||
let database: Database;
|
let database: Database;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
@ -37,10 +34,6 @@ describe("Delete Database Confirmation Pane", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fakeExplorer = {} as Explorer;
|
|
||||||
fakeExplorer.refreshAllDatabases = () => undefined;
|
|
||||||
fakeExplorer.tabsManager = new TabsManager();
|
|
||||||
|
|
||||||
database = {} as Database;
|
database = {} as Database;
|
||||||
database.collections = ko.observableArray<Collection>([{ id: ko.observable("testCollection") } as Collection]);
|
database.collections = ko.observableArray<Collection>([{ id: ko.observable("testCollection") } as Collection]);
|
||||||
database.id = ko.observable<string>(selectedDatabaseId);
|
database.id = ko.observable<string>(selectedDatabaseId);
|
||||||
@ -56,17 +49,17 @@ describe("Delete Database Confirmation Pane", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("shouldRecordFeedback() should return true if last non empty database or is last database that has shared throughput", () => {
|
it("shouldRecordFeedback() should return true if last non empty database or is last database that has shared throughput", () => {
|
||||||
const wrapper = shallow(<DeleteDatabaseConfirmationPanel explorer={fakeExplorer} />);
|
const wrapper = shallow(<DeleteDatabaseConfirmationPanel refreshDatabases={() => undefined} />);
|
||||||
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(true);
|
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(true);
|
||||||
|
|
||||||
useDatabases.getState().addDatabases([database]);
|
useDatabases.getState().addDatabases([database]);
|
||||||
wrapper.setProps({ explorer: fakeExplorer });
|
wrapper.setProps({});
|
||||||
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(false);
|
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(false);
|
||||||
useDatabases.getState().clearDatabases();
|
useDatabases.getState().clearDatabases();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should call delete database", () => {
|
it("Should call delete database", () => {
|
||||||
const wrapper = mount(<DeleteDatabaseConfirmationPanel explorer={fakeExplorer} />);
|
const wrapper = mount(<DeleteDatabaseConfirmationPanel refreshDatabases={() => undefined} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
|
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
|
||||||
|
|
||||||
@ -81,7 +74,7 @@ describe("Delete Database Confirmation Pane", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should record feedback", async () => {
|
it("should record feedback", async () => {
|
||||||
const wrapper = mount(<DeleteDatabaseConfirmationPanel explorer={fakeExplorer} />);
|
const wrapper = mount(<DeleteDatabaseConfirmationPanel refreshDatabases={() => undefined} />);
|
||||||
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
|
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
|
||||||
wrapper
|
wrapper
|
||||||
.find("#confirmDatabaseId")
|
.find("#confirmDatabaseId")
|
||||||
|
@ -7,23 +7,23 @@ import DeleteFeedback from "../../Common/DeleteFeedback";
|
|||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
import { Collection, Database } from "../../Contracts/ViewModels";
|
import { Collection, Database } from "../../Contracts/ViewModels";
|
||||||
import { useSidePanel } from "../../hooks/useSidePanel";
|
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||||
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../Explorer";
|
|
||||||
import { useDatabases } from "../useDatabases";
|
import { useDatabases } from "../useDatabases";
|
||||||
import { useSelectedNode } from "../useSelectedNode";
|
import { useSelectedNode } from "../useSelectedNode";
|
||||||
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
|
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "./RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "./RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
interface DeleteDatabaseConfirmationPanelProps {
|
interface DeleteDatabaseConfirmationPanelProps {
|
||||||
explorer: Explorer;
|
refreshDatabases: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseConfirmationPanelProps> = ({
|
export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseConfirmationPanelProps> = ({
|
||||||
explorer,
|
refreshDatabases,
|
||||||
}: DeleteDatabaseConfirmationPanelProps): JSX.Element => {
|
}: DeleteDatabaseConfirmationPanelProps): JSX.Element => {
|
||||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
const isLastNonEmptyDatabase = useDatabases((state) => state.isLastNonEmptyDatabase);
|
const isLastNonEmptyDatabase = useDatabases((state) => state.isLastNonEmptyDatabase);
|
||||||
@ -32,7 +32,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
const [formError, setFormError] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
const [databaseInput, setDatabaseInput] = useState<string>("");
|
const [databaseInput, setDatabaseInput] = useState<string>("");
|
||||||
const [databaseFeedbackInput, setDatabaseFeedbackInput] = useState<string>("");
|
const [databaseFeedbackInput, setDatabaseFeedbackInput] = useState<string>("");
|
||||||
const selectedDatabase: Database = useSelectedNode.getState().findSelectedDatabase();
|
const selectedDatabase: Database = useDatabases.getState().findSelectedDatabase();
|
||||||
|
|
||||||
const submit = async (): Promise<void> => {
|
const submit = async (): Promise<void> => {
|
||||||
if (selectedDatabase?.id() && databaseInput !== selectedDatabase.id()) {
|
if (selectedDatabase?.id() && databaseInput !== selectedDatabase.id()) {
|
||||||
@ -52,14 +52,17 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
try {
|
try {
|
||||||
await deleteDatabase(selectedDatabase.id());
|
await deleteDatabase(selectedDatabase.id());
|
||||||
closeSidePanel();
|
closeSidePanel();
|
||||||
explorer.refreshAllDatabases();
|
refreshDatabases();
|
||||||
explorer.tabsManager.closeTabsByComparator((tab) => tab.node?.id() === selectedDatabase.id());
|
useTabs.getState().closeTabsByComparator((tab) => tab.node?.id() === selectedDatabase.id());
|
||||||
useSelectedNode.getState().setSelectedNode(undefined);
|
useSelectedNode.getState().setSelectedNode(undefined);
|
||||||
selectedDatabase
|
selectedDatabase
|
||||||
.collections()
|
.collections()
|
||||||
.forEach((collection: Collection) =>
|
.forEach((collection: Collection) =>
|
||||||
explorer.tabsManager.closeTabsByComparator(
|
useTabs
|
||||||
(tab) => tab.node?.id() === collection.id() && (tab.node as Collection).databaseId === collection.databaseId
|
.getState()
|
||||||
|
.closeTabsByComparator(
|
||||||
|
(tab) =>
|
||||||
|
tab.node?.id() === collection.id() && (tab.node as Collection).databaseId === collection.databaseId
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
TelemetryProcessor.traceSuccess(
|
TelemetryProcessor.traceSuccess(
|
||||||
|
@ -20,7 +20,6 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
|||||||
"_isInitializingNotebooks": false,
|
"_isInitializingNotebooks": false,
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
"isSchemaEnabled": [Function],
|
|
||||||
"isTabsContentExpanded": [Function],
|
"isTabsContentExpanded": [Function],
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
"onRefreshDatabasesKeyPress": [Function],
|
||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
@ -38,10 +37,6 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
|||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"parameters": [Function],
|
"parameters": [Function],
|
||||||
},
|
},
|
||||||
"tabsManager": TabsManager {
|
|
||||||
"activeTab": [Function],
|
|
||||||
"openedTabs": [Function],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"getRepo": [Function],
|
"getRepo": [Function],
|
||||||
"pinRepo": [Function],
|
"pinRepo": [Function],
|
||||||
|
@ -5,6 +5,7 @@ import { Areas, SavedQueries } from "../../../Common/Constants";
|
|||||||
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||||
import { Query } from "../../../Contracts/DataModels";
|
import { Query } from "../../../Contracts/DataModels";
|
||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||||
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||||
@ -34,7 +35,7 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({ explorer
|
|||||||
logConsoleError("Failed to save query: account not setup to save queries");
|
logConsoleError("Failed to save query: account not setup to save queries");
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryTab = explorer && (explorer.tabsManager.activeTab() as NewQueryTab);
|
const queryTab = useTabs.getState().activeTab as NewQueryTab;
|
||||||
const query: string = queryTab && queryTab.iTabAccessor.onSaveClickEvent();
|
const query: string = queryTab && queryTab.iTabAccessor.onSaveClickEvent();
|
||||||
|
|
||||||
if (!queryName || queryName.length === 0) {
|
if (!queryName || queryName.length === 0) {
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import { TextField } from "@fluentui/react";
|
import { TextField } from "@fluentui/react";
|
||||||
import React, { FormEvent, FunctionComponent, useState } from "react";
|
import React, { FormEvent, FunctionComponent, useState } from "react";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
|
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
|
||||||
import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
|
import * as FileSystemUtil from "../../Notebook/FileSystemUtil";
|
||||||
import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
|
import { NotebookContentItem } from "../../Notebook/NotebookContentItem";
|
||||||
import NotebookV2Tab from "../../Tabs/NotebookV2Tab";
|
import NotebookV2Tab from "../../Tabs/NotebookV2Tab";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
export interface StringInputPanelProps {
|
export interface StringInputPanelProps {
|
||||||
explorer: Explorer;
|
|
||||||
closePanel: () => void;
|
closePanel: () => void;
|
||||||
errorMessage: string;
|
errorMessage: string;
|
||||||
inProgressMessage: string;
|
inProgressMessage: string;
|
||||||
@ -23,7 +22,6 @@ export interface StringInputPanelProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
|
export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
|
||||||
explorer: container,
|
|
||||||
closePanel,
|
closePanel,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
inProgressMessage,
|
inProgressMessage,
|
||||||
@ -55,7 +53,9 @@ export const StringInputPane: FunctionComponent<StringInputPanelProps> = ({
|
|||||||
logConsoleInfo(`${successMessage}: ${stringInput}`);
|
logConsoleInfo(`${successMessage}: ${stringInput}`);
|
||||||
const originalPath = notebookFile.path;
|
const originalPath = notebookFile.path;
|
||||||
|
|
||||||
const notebookTabs = container.tabsManager.getTabs(
|
const notebookTabs = useTabs
|
||||||
|
.getState()
|
||||||
|
.getTabs(
|
||||||
ViewModels.CollectionTabKind.NotebookV2,
|
ViewModels.CollectionTabKind.NotebookV2,
|
||||||
(tab: NotebookV2Tab) => tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), originalPath)
|
(tab: NotebookV2Tab) => tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), originalPath)
|
||||||
);
|
);
|
||||||
|
@ -10,7 +10,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
"_isInitializingNotebooks": false,
|
"_isInitializingNotebooks": false,
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
"isSchemaEnabled": [Function],
|
|
||||||
"isTabsContentExpanded": [Function],
|
"isTabsContentExpanded": [Function],
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
"onRefreshDatabasesKeyPress": [Function],
|
||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
@ -28,10 +27,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"parameters": [Function],
|
"parameters": [Function],
|
||||||
},
|
},
|
||||||
"tabsManager": TabsManager {
|
|
||||||
"activeTab": [Function],
|
|
||||||
"openedTabs": [Function],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inProgressMessage="Creating directory "
|
inProgressMessage="Creating directory "
|
||||||
|
@ -2,15 +2,7 @@
|
|||||||
|
|
||||||
exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
||||||
<DeleteDatabaseConfirmationPanel
|
<DeleteDatabaseConfirmationPanel
|
||||||
explorer={
|
refreshDatabases={[Function]}
|
||||||
Object {
|
|
||||||
"refreshAllDatabases": [Function],
|
|
||||||
"tabsManager": TabsManager {
|
|
||||||
"activeTab": [Function],
|
|
||||||
"openedTabs": [Function],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<RightPaneForm
|
<RightPaneForm
|
||||||
formError=""
|
formError=""
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
|
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { TabsManager } from "../Tabs/TabsManager";
|
|
||||||
import { SplashScreen } from "./SplashScreen";
|
import { SplashScreen } from "./SplashScreen";
|
||||||
jest.mock("../Explorer");
|
jest.mock("../Explorer");
|
||||||
|
|
||||||
const createExplorer = () => {
|
const createExplorer = () => {
|
||||||
const mock = new Explorer();
|
const mock = new Explorer();
|
||||||
mock.tabsManager = new TabsManager();
|
|
||||||
return mock as jest.Mocked<Explorer>;
|
return mock as jest.Mocked<Explorer>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -280,7 +280,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Scale & Settings */
|
/* Scale & Settings */
|
||||||
const isShared = useSelectedNode.getState().findSelectedDatabase()?.isDatabaseShared();
|
const isShared = useDatabases.getState().findSelectedDatabase()?.isDatabaseShared();
|
||||||
|
|
||||||
const label = isShared ? "Settings" : "Scale & Settings";
|
const label = isShared ? "Settings" : "Scale & Settings";
|
||||||
items.push({
|
items.push({
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import type { TabOptions } from "../../../Contracts/ViewModels";
|
import type { TabOptions } from "../../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import TabsBase from "../TabsBase";
|
import TabsBase from "../TabsBase";
|
||||||
import MongoShellTabComponent, { IMongoShellTabAccessor, IMongoShellTabComponentProps } from "./MongoShellTabComponent";
|
import MongoShellTabComponent, { IMongoShellTabAccessor, IMongoShellTabComponentProps } from "./MongoShellTabComponent";
|
||||||
@ -33,7 +34,7 @@ export class NewMongoShellTab extends TabsBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): void {
|
public onTabClick(): void {
|
||||||
this.manager?.activateTab(this);
|
useTabs.getState().activateTab(this);
|
||||||
this.iMongoShellTabAccessor.onTabClickEvent();
|
this.iMongoShellTabAccessor.onTabClickEvent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import type { QueryTabOptions } from "../../../Contracts/ViewModels";
|
import type { QueryTabOptions } from "../../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { IQueryTabComponentProps, ITabAccessor } from "../../Tabs/QueryTab/QueryTabComponent";
|
import { IQueryTabComponentProps, ITabAccessor } from "../../Tabs/QueryTab/QueryTabComponent";
|
||||||
import TabsBase from "../TabsBase";
|
import TabsBase from "../TabsBase";
|
||||||
@ -40,12 +41,12 @@ export class NewQueryTab extends TabsBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): void {
|
public onTabClick(): void {
|
||||||
this.manager?.activateTab(this);
|
useTabs.getState().activateTab(this);
|
||||||
this.iTabAccessor.onTabClickEvent();
|
this.iTabAccessor.onTabClickEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onCloseTabButtonClick(): void {
|
public onCloseTabButtonClick(): void {
|
||||||
this.manager?.closeTab(this);
|
useTabs.getState().closeTab(this);
|
||||||
if (this.iTabAccessor) {
|
if (this.iTabAccessor) {
|
||||||
this.iTabAccessor.onCloseClickEvent(true);
|
this.iTabAccessor.onCloseClickEvent(true);
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,6 @@ import { EditorReact } from "../../Controls/Editor/EditorReact";
|
|||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
|
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import TabsBase from "../TabsBase";
|
import TabsBase from "../TabsBase";
|
||||||
import { TabsManager } from "../TabsManager";
|
|
||||||
import "./QueryTabComponent.less";
|
import "./QueryTabComponent.less";
|
||||||
|
|
||||||
enum ToggleState {
|
enum ToggleState {
|
||||||
@ -65,7 +64,6 @@ export interface IQueryTabComponentProps {
|
|||||||
partitionKey: DataModels.PartitionKey;
|
partitionKey: DataModels.PartitionKey;
|
||||||
container: Explorer;
|
container: Explorer;
|
||||||
activeTab?: TabsBase;
|
activeTab?: TabsBase;
|
||||||
tabManager?: TabsManager;
|
|
||||||
onTabAccessor: (instance: ITabAccessor) => void;
|
onTabAccessor: (instance: ITabAccessor) => void;
|
||||||
isPreferredApiMongoDB?: boolean;
|
isPreferredApiMongoDB?: boolean;
|
||||||
monacoEditorSetting?: string;
|
monacoEditorSetting?: string;
|
||||||
|
@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import { ExecuteSprocResult } from "../../../Common/dataAccess/executeStoredProcedure";
|
import { ExecuteSprocResult } from "../../../Common/dataAccess/executeStoredProcedure";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import StoredProcedure from "../../Tree/StoredProcedure";
|
import StoredProcedure from "../../Tree/StoredProcedure";
|
||||||
import ScriptTabBase from "../ScriptTabBase";
|
import ScriptTabBase from "../ScriptTabBase";
|
||||||
@ -51,12 +52,12 @@ export class NewStoredProcedureTab extends ScriptTabBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): void {
|
public onTabClick(): void {
|
||||||
this.manager?.activateTab(this);
|
useTabs.getState().activateTab(this);
|
||||||
this.iStoreProcAccessor.onTabClickEvent();
|
this.iStoreProcAccessor.onTabClickEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onCloseTabButtonClick(): void {
|
public onCloseTabButtonClick(): void {
|
||||||
this.manager?.closeTab(this);
|
useTabs.getState().closeTab(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onExecuteSprocsResult(result: ExecuteSprocResult): void {
|
public onExecuteSprocsResult(result: ExecuteSprocResult): void {
|
||||||
|
@ -10,6 +10,7 @@ import { ExecuteSprocResult } from "../../../Common/dataAccess/executeStoredProc
|
|||||||
import { updateStoredProcedure } from "../../../Common/dataAccess/updateStoredProcedure";
|
import { updateStoredProcedure } from "../../../Common/dataAccess/updateStoredProcedure";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import { useNotificationConsole } from "../../../hooks/useNotificationConsole";
|
import { useNotificationConsole } from "../../../hooks/useNotificationConsole";
|
||||||
|
import { useTabs } from "../../../hooks/useTabs";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import { EditorReact } from "../../Controls/Editor/EditorReact";
|
import { EditorReact } from "../../Controls/Editor/EditorReact";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
@ -144,7 +145,7 @@ export default class StoredProcedureTabComponent extends React.Component<
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): void {
|
public onTabClick(): void {
|
||||||
if (this.props.container.tabsManager.openedTabs().length > 0) {
|
if (useTabs.getState().openedTabs.length > 0) {
|
||||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -396,10 +397,8 @@ export default class StoredProcedureTabComponent extends React.Component<
|
|||||||
editorModel && editorModel.setValue(createdResource.body as string);
|
editorModel && editorModel.setValue(createdResource.body as string);
|
||||||
this.props.scriptTabBaseInstance.editorContent.setBaseline(createdResource.body as string);
|
this.props.scriptTabBaseInstance.editorContent.setBaseline(createdResource.body as string);
|
||||||
this.node = this.collection.createStoredProcedureNode(createdResource);
|
this.node = this.collection.createStoredProcedureNode(createdResource);
|
||||||
this.props.container.tabsManager.openedTabs()[
|
this.props.scriptTabBaseInstance.node = this.node;
|
||||||
this.props.container.tabsManager.openedTabs().length - 1
|
useTabs.getState().updateTab(this.props.scriptTabBaseInstance);
|
||||||
].node = this.node;
|
|
||||||
|
|
||||||
this.props.scriptTabBaseInstance.editorState(ViewModels.ScriptEditorState.exisitingNoEdits);
|
this.props.scriptTabBaseInstance.editorState(ViewModels.ScriptEditorState.exisitingNoEdits);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -3,28 +3,32 @@ import React, { useEffect, useRef, useState } from "react";
|
|||||||
import loadingIcon from "../../../images/circular_loader_black_16x16.gif";
|
import loadingIcon from "../../../images/circular_loader_black_16x16.gif";
|
||||||
import errorIcon from "../../../images/close-black.svg";
|
import errorIcon from "../../../images/close-black.svg";
|
||||||
import { useObservable } from "../../hooks/useObservable";
|
import { useObservable } from "../../hooks/useObservable";
|
||||||
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
import TabsBase from "./TabsBase";
|
import TabsBase from "./TabsBase";
|
||||||
|
|
||||||
type Tab = TabsBase | (TabsBase & { render: () => JSX.Element });
|
type Tab = TabsBase | (TabsBase & { render: () => JSX.Element });
|
||||||
|
|
||||||
export const Tabs = ({ tabs, activeTab }: { tabs: readonly Tab[]; activeTab: Tab }): JSX.Element => (
|
export const Tabs = (): JSX.Element => {
|
||||||
|
const { openedTabs, activeTab } = useTabs();
|
||||||
|
return (
|
||||||
<div className="tabsManagerContainer">
|
<div className="tabsManagerContainer">
|
||||||
<div id="content" className="flexContainer hideOverflows">
|
<div id="content" className="flexContainer hideOverflows">
|
||||||
<div className="nav-tabs-margin">
|
<div className="nav-tabs-margin">
|
||||||
<ul className="nav nav-tabs level navTabHeight" id="navTabs" role="tablist">
|
<ul className="nav nav-tabs level navTabHeight" id="navTabs" role="tablist">
|
||||||
{tabs.map((tab) => (
|
{openedTabs.map((tab) => (
|
||||||
<TabNav key={tab.tabId} tab={tab} active={activeTab === tab} />
|
<TabNav key={tab.tabId} tab={tab} active={activeTab === tab} />
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="tabPanesContainer">
|
<div className="tabPanesContainer">
|
||||||
{tabs.map((tab) => (
|
{openedTabs.map((tab) => (
|
||||||
<TabPane key={tab.tabId} tab={tab} active={activeTab === tab} />
|
<TabPane key={tab.tabId} tab={tab} active={activeTab === tab} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
function TabNav({ tab, active }: { tab: Tab; active: boolean }) {
|
function TabNav({ tab, active }: { tab: Tab; active: boolean }) {
|
||||||
const [hovering, setHovering] = useState(false);
|
const [hovering, setHovering] = useState(false);
|
||||||
|
@ -4,6 +4,7 @@ import * as ThemeUtility from "../../Common/ThemeUtility";
|
|||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { useNotificationConsole } from "../../hooks/useNotificationConsole";
|
import { useNotificationConsole } from "../../hooks/useNotificationConsole";
|
||||||
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||||
@ -11,7 +12,6 @@ import Explorer from "../Explorer";
|
|||||||
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
|
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import { useSelectedNode } from "../useSelectedNode";
|
import { useSelectedNode } from "../useSelectedNode";
|
||||||
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
|
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
|
||||||
import { TabsManager } from "./TabsManager";
|
|
||||||
// TODO: Use specific actions for logging telemetry data
|
// TODO: Use specific actions for logging telemetry data
|
||||||
export default class TabsBase extends WaitsForTemplateViewModel {
|
export default class TabsBase extends WaitsForTemplateViewModel {
|
||||||
private static id = 0;
|
private static id = 0;
|
||||||
@ -28,7 +28,6 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
public isExecutionError = ko.observable(false);
|
public isExecutionError = ko.observable(false);
|
||||||
public isExecuting = ko.observable(false);
|
public isExecuting = ko.observable(false);
|
||||||
public pendingNotification?: ko.Observable<DataModels.Notification>;
|
public pendingNotification?: ko.Observable<DataModels.Notification>;
|
||||||
public manager?: TabsManager;
|
|
||||||
protected _theme: string;
|
protected _theme: string;
|
||||||
public onLoadStartKey: number;
|
public onLoadStartKey: number;
|
||||||
|
|
||||||
@ -60,7 +59,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onCloseTabButtonClick(): void {
|
public onCloseTabButtonClick(): void {
|
||||||
this.manager?.closeTab(this);
|
useTabs.getState().closeTab(this);
|
||||||
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, {
|
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, {
|
||||||
tabName: this.constructor.name,
|
tabName: this.constructor.name,
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
@ -70,7 +69,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): void {
|
public onTabClick(): void {
|
||||||
this.manager?.activateTab(this);
|
useTabs.getState().activateTab(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updateSelectedNode(): void {
|
protected updateSelectedNode(): void {
|
||||||
@ -105,7 +104,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
|
|
||||||
/** @deprecated this is no longer observable, bind to comparisons with manager.activeTab() instead */
|
/** @deprecated this is no longer observable, bind to comparisons with manager.activeTab() instead */
|
||||||
public isActive() {
|
public isActive() {
|
||||||
return this === this.manager?.activeTab();
|
return this === useTabs.getState().activeTab;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onActivate(): void {
|
public onActivate(): void {
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
import * as ko from "knockout";
|
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
|
||||||
import TabsBase from "./TabsBase";
|
|
||||||
|
|
||||||
export class TabsManager {
|
|
||||||
public openedTabs = ko.observableArray<TabsBase>([]);
|
|
||||||
public activeTab = ko.observable<TabsBase>();
|
|
||||||
|
|
||||||
public activateNewTab(tab: TabsBase): void {
|
|
||||||
this.openedTabs.push(tab);
|
|
||||||
this.activateTab(tab);
|
|
||||||
}
|
|
||||||
|
|
||||||
public activateTab(tab: TabsBase): void {
|
|
||||||
if (this.openedTabs().includes(tab)) {
|
|
||||||
tab.manager = this;
|
|
||||||
this.activeTab(tab);
|
|
||||||
tab.onActivate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTabs(tabKind: ViewModels.CollectionTabKind, comparator?: (tab: TabsBase) => boolean): TabsBase[] {
|
|
||||||
return this.openedTabs().filter((tab) => tab.tabKind === tabKind && (!comparator || comparator(tab)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public refreshActiveTab(comparator: (tab: TabsBase) => boolean): void {
|
|
||||||
// ensures that the tab selects/highlights the right node based on resource tree expand/collapse state
|
|
||||||
this.activeTab() && comparator(this.activeTab()) && this.activeTab().onActivate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public closeTabsByComparator(comparator: (tab: TabsBase) => boolean): void {
|
|
||||||
this.openedTabs()
|
|
||||||
.filter(comparator)
|
|
||||||
.forEach((tab) => tab.onCloseTabButtonClick());
|
|
||||||
}
|
|
||||||
|
|
||||||
public closeTab(tab: TabsBase): void {
|
|
||||||
const tabIndex = this.openedTabs().indexOf(tab);
|
|
||||||
if (tabIndex !== -1) {
|
|
||||||
this.openedTabs.remove(tab);
|
|
||||||
tab.manager = undefined;
|
|
||||||
|
|
||||||
if (this.openedTabs().length === 0) {
|
|
||||||
this.activeTab(undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tab === this.activeTab()) {
|
|
||||||
const tabToTheRight = this.openedTabs()[tabIndex];
|
|
||||||
const lastOpenTab = this.openedTabs()[this.openedTabs().length - 1];
|
|
||||||
this.activateTab(tabToTheRight ?? lastOpenTab);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +1,19 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
import { updateUserContext } from "../../UserContext";
|
import { updateUserContext } from "../../UserContext";
|
||||||
import Explorer from "../Explorer";
|
import { container } from "../Controls/Settings/TestUtils";
|
||||||
import DocumentId from "../Tree/DocumentId";
|
import DocumentId from "../Tree/DocumentId";
|
||||||
import { container } from "./../Controls/Settings/TestUtils";
|
|
||||||
import DocumentsTab from "./DocumentsTab";
|
import DocumentsTab from "./DocumentsTab";
|
||||||
import { NewQueryTab } from "./QueryTab/QueryTab";
|
import { NewQueryTab } from "./QueryTab/QueryTab";
|
||||||
import { TabsManager } from "./TabsManager";
|
|
||||||
|
|
||||||
describe("Tabs manager tests", () => {
|
describe("useTabs tests", () => {
|
||||||
let tabsManager: TabsManager;
|
|
||||||
let explorer: Explorer;
|
|
||||||
let database: ViewModels.Database;
|
let database: ViewModels.Database;
|
||||||
let collection: ViewModels.Collection;
|
let collection: ViewModels.Collection;
|
||||||
let queryTab: NewQueryTab;
|
let queryTab: NewQueryTab;
|
||||||
let documentsTab: DocumentsTab;
|
let documentsTab: DocumentsTab;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = new Explorer();
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
databaseAccount: {
|
databaseAccount: {
|
||||||
id: "test",
|
id: "test",
|
||||||
@ -30,7 +26,6 @@ describe("Tabs manager tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
database = {
|
database = {
|
||||||
container: explorer,
|
|
||||||
id: ko.observable<string>("test"),
|
id: ko.observable<string>("test"),
|
||||||
isDatabaseShared: () => false,
|
isDatabaseShared: () => false,
|
||||||
} as ViewModels.Database;
|
} as ViewModels.Database;
|
||||||
@ -38,7 +33,6 @@ describe("Tabs manager tests", () => {
|
|||||||
database.selectedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
|
database.selectedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
|
||||||
|
|
||||||
collection = {
|
collection = {
|
||||||
container: explorer,
|
|
||||||
databaseId: "test",
|
databaseId: "test",
|
||||||
id: ko.observable<string>("test"),
|
id: ko.observable<string>("test"),
|
||||||
} as ViewModels.Collection;
|
} as ViewModels.Collection;
|
||||||
@ -76,63 +70,70 @@ describe("Tabs manager tests", () => {
|
|||||||
documentsTab.tabId = "2";
|
documentsTab.tabId = "2";
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => (tabsManager = new TabsManager()));
|
beforeEach(() => useTabs.setState({ openedTabs: [], activeTab: undefined }));
|
||||||
|
|
||||||
it("open new tabs", () => {
|
it("open new tabs", () => {
|
||||||
tabsManager.activateNewTab(queryTab);
|
const { activateNewTab } = useTabs.getState();
|
||||||
expect(tabsManager.openedTabs().length).toBe(1);
|
activateNewTab(queryTab);
|
||||||
expect(tabsManager.openedTabs()[0]).toEqual(queryTab);
|
let tabsState = useTabs.getState();
|
||||||
expect(tabsManager.activeTab()).toEqual(queryTab);
|
expect(tabsState.openedTabs.length).toBe(1);
|
||||||
|
expect(tabsState.openedTabs[0]).toEqual(queryTab);
|
||||||
|
expect(tabsState.activeTab).toEqual(queryTab);
|
||||||
expect(queryTab.isActive()).toBe(true);
|
expect(queryTab.isActive()).toBe(true);
|
||||||
|
|
||||||
tabsManager.activateNewTab(documentsTab);
|
activateNewTab(documentsTab);
|
||||||
expect(tabsManager.openedTabs().length).toBe(2);
|
tabsState = useTabs.getState();
|
||||||
expect(tabsManager.openedTabs()[1]).toEqual(documentsTab);
|
expect(tabsState.openedTabs.length).toBe(2);
|
||||||
expect(tabsManager.activeTab()).toEqual(documentsTab);
|
expect(tabsState.openedTabs[1]).toEqual(documentsTab);
|
||||||
|
expect(tabsState.activeTab).toEqual(documentsTab);
|
||||||
expect(queryTab.isActive()).toBe(false);
|
expect(queryTab.isActive()).toBe(false);
|
||||||
expect(documentsTab.isActive()).toBe(true);
|
expect(documentsTab.isActive()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("open existing tabs", () => {
|
it("open existing tabs", () => {
|
||||||
tabsManager.activateNewTab(queryTab);
|
const { activateNewTab, activateTab } = useTabs.getState();
|
||||||
tabsManager.activateNewTab(documentsTab);
|
activateNewTab(queryTab);
|
||||||
tabsManager.activateTab(queryTab);
|
activateNewTab(documentsTab);
|
||||||
expect(tabsManager.openedTabs().length).toBe(2);
|
activateTab(queryTab);
|
||||||
expect(tabsManager.activeTab()).toEqual(queryTab);
|
|
||||||
|
const { openedTabs, activeTab } = useTabs.getState();
|
||||||
|
expect(openedTabs.length).toBe(2);
|
||||||
|
expect(activeTab).toEqual(queryTab);
|
||||||
expect(queryTab.isActive()).toBe(true);
|
expect(queryTab.isActive()).toBe(true);
|
||||||
expect(documentsTab.isActive()).toBe(false);
|
expect(documentsTab.isActive()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("get tabs", () => {
|
it("get tabs", () => {
|
||||||
tabsManager.activateNewTab(queryTab);
|
const { activateNewTab, getTabs } = useTabs.getState();
|
||||||
tabsManager.activateNewTab(documentsTab);
|
activateNewTab(queryTab);
|
||||||
|
activateNewTab(documentsTab);
|
||||||
|
|
||||||
const queryTabs = tabsManager.getTabs(ViewModels.CollectionTabKind.Query);
|
const queryTabs = getTabs(ViewModels.CollectionTabKind.Query);
|
||||||
expect(queryTabs.length).toBe(1);
|
expect(queryTabs.length).toBe(1);
|
||||||
expect(queryTabs[0]).toEqual(queryTab);
|
expect(queryTabs[0]).toEqual(queryTab);
|
||||||
|
|
||||||
const documentsTabs = tabsManager.getTabs(
|
const documentsTabs = getTabs(ViewModels.CollectionTabKind.Documents, (tab) => tab.tabId === documentsTab.tabId);
|
||||||
ViewModels.CollectionTabKind.Documents,
|
|
||||||
(tab) => tab.tabId === documentsTab.tabId
|
|
||||||
);
|
|
||||||
expect(documentsTabs.length).toBe(1);
|
expect(documentsTabs.length).toBe(1);
|
||||||
expect(documentsTabs[0]).toEqual(documentsTab);
|
expect(documentsTabs[0]).toEqual(documentsTab);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("close tabs", () => {
|
it("close tabs", () => {
|
||||||
tabsManager.activateNewTab(queryTab);
|
const { activateNewTab, closeTab, closeTabsByComparator } = useTabs.getState();
|
||||||
tabsManager.activateNewTab(documentsTab);
|
activateNewTab(queryTab);
|
||||||
|
activateNewTab(documentsTab);
|
||||||
|
closeTab(documentsTab);
|
||||||
|
|
||||||
tabsManager.closeTab(documentsTab);
|
let tabsState = useTabs.getState();
|
||||||
expect(tabsManager.openedTabs().length).toBe(1);
|
expect(tabsState.openedTabs.length).toBe(1);
|
||||||
expect(tabsManager.openedTabs()[0]).toEqual(queryTab);
|
expect(tabsState.openedTabs[0]).toEqual(queryTab);
|
||||||
expect(tabsManager.activeTab()).toEqual(queryTab);
|
expect(tabsState.activeTab).toEqual(queryTab);
|
||||||
expect(queryTab.isActive()).toBe(true);
|
expect(queryTab.isActive()).toBe(true);
|
||||||
expect(documentsTab.isActive()).toBe(false);
|
expect(documentsTab.isActive()).toBe(false);
|
||||||
|
|
||||||
tabsManager.closeTabsByComparator((tab) => tab.tabId === queryTab.tabId);
|
closeTabsByComparator((tab) => tab.tabId === queryTab.tabId);
|
||||||
expect(tabsManager.openedTabs().length).toBe(0);
|
tabsState = useTabs.getState();
|
||||||
expect(tabsManager.activeTab()).toEqual(undefined);
|
expect(tabsState.openedTabs.length).toBe(0);
|
||||||
|
expect(tabsState.activeTab).toEqual(undefined);
|
||||||
expect(queryTab.isActive()).toBe(false);
|
expect(queryTab.isActive()).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -15,6 +15,7 @@ import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
|||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { UploadDetailsRecord } from "../../Contracts/ViewModels";
|
import { UploadDetailsRecord } from "../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
@ -239,7 +240,9 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
this.expandCollection();
|
this.expandCollection();
|
||||||
}
|
}
|
||||||
useCommandBar.getState().setContextButtons([]);
|
useCommandBar.getState().setContextButtons([]);
|
||||||
this.container.tabsManager.refreshActiveTab(
|
useTabs
|
||||||
|
.getState()
|
||||||
|
.refreshActiveTab(
|
||||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -288,14 +291,16 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
|
|
||||||
const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
|
const documentsTabs: DocumentsTab[] = useTabs
|
||||||
|
.getState()
|
||||||
|
.getTabs(
|
||||||
ViewModels.CollectionTabKind.Documents,
|
ViewModels.CollectionTabKind.Documents,
|
||||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
) as DocumentsTab[];
|
) as DocumentsTab[];
|
||||||
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
|
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
|
||||||
|
|
||||||
if (documentsTab) {
|
if (documentsTab) {
|
||||||
this.container.tabsManager.activateTab(documentsTab);
|
useTabs.getState().activateTab(documentsTab);
|
||||||
} else {
|
} else {
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||||
databaseName: this.databaseId,
|
databaseName: this.databaseId,
|
||||||
@ -317,7 +322,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.container.tabsManager.activateNewTab(documentsTab);
|
useTabs.getState().activateNewTab(documentsTab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,14 +338,16 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
|
|
||||||
const conflictsTabs: ConflictsTab[] = this.container.tabsManager.getTabs(
|
const conflictsTabs: ConflictsTab[] = useTabs
|
||||||
|
.getState()
|
||||||
|
.getTabs(
|
||||||
ViewModels.CollectionTabKind.Conflicts,
|
ViewModels.CollectionTabKind.Conflicts,
|
||||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
) as ConflictsTab[];
|
) as ConflictsTab[];
|
||||||
let conflictsTab: ConflictsTab = conflictsTabs && conflictsTabs[0];
|
let conflictsTab: ConflictsTab = conflictsTabs && conflictsTabs[0];
|
||||||
|
|
||||||
if (conflictsTab) {
|
if (conflictsTab) {
|
||||||
this.container.tabsManager.activateTab(conflictsTab);
|
useTabs.getState().activateTab(conflictsTab);
|
||||||
} else {
|
} else {
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||||
databaseName: this.databaseId,
|
databaseName: this.databaseId,
|
||||||
@ -362,7 +369,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.container.tabsManager.activateNewTab(conflictsTab);
|
useTabs.getState().activateNewTab(conflictsTab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -384,14 +391,16 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryTablesTabs: QueryTablesTab[] = this.container.tabsManager.getTabs(
|
const queryTablesTabs: QueryTablesTab[] = useTabs
|
||||||
|
.getState()
|
||||||
|
.getTabs(
|
||||||
ViewModels.CollectionTabKind.QueryTables,
|
ViewModels.CollectionTabKind.QueryTables,
|
||||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
) as QueryTablesTab[];
|
) as QueryTablesTab[];
|
||||||
let queryTablesTab: QueryTablesTab = queryTablesTabs && queryTablesTabs[0];
|
let queryTablesTab: QueryTablesTab = queryTablesTabs && queryTablesTabs[0];
|
||||||
|
|
||||||
if (queryTablesTab) {
|
if (queryTablesTab) {
|
||||||
this.container.tabsManager.activateTab(queryTablesTab);
|
useTabs.getState().activateTab(queryTablesTab);
|
||||||
} else {
|
} else {
|
||||||
this.documentIds([]);
|
this.documentIds([]);
|
||||||
let title = `Entities`;
|
let title = `Entities`;
|
||||||
@ -415,7 +424,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.container.tabsManager.activateNewTab(queryTablesTab);
|
useTabs.getState().activateNewTab(queryTablesTab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,14 +440,16 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
|
|
||||||
const graphTabs: GraphTab[] = this.container.tabsManager.getTabs(
|
const graphTabs: GraphTab[] = useTabs
|
||||||
|
.getState()
|
||||||
|
.getTabs(
|
||||||
ViewModels.CollectionTabKind.Graph,
|
ViewModels.CollectionTabKind.Graph,
|
||||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
) as GraphTab[];
|
) as GraphTab[];
|
||||||
let graphTab: GraphTab = graphTabs && graphTabs[0];
|
let graphTab: GraphTab = graphTabs && graphTabs[0];
|
||||||
|
|
||||||
if (graphTab) {
|
if (graphTab) {
|
||||||
this.container.tabsManager.activateTab(graphTab);
|
useTabs.getState().activateTab(graphTab);
|
||||||
} else {
|
} else {
|
||||||
this.documentIds([]);
|
this.documentIds([]);
|
||||||
const title = "Graph";
|
const title = "Graph";
|
||||||
@ -466,7 +477,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.container.tabsManager.activateNewTab(graphTab);
|
useTabs.getState().activateNewTab(graphTab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -482,14 +493,16 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mongoDocumentsTabs: MongoDocumentsTab[] = this.container.tabsManager.getTabs(
|
const mongoDocumentsTabs: MongoDocumentsTab[] = useTabs
|
||||||
|
.getState()
|
||||||
|
.getTabs(
|
||||||
ViewModels.CollectionTabKind.Documents,
|
ViewModels.CollectionTabKind.Documents,
|
||||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
) as MongoDocumentsTab[];
|
) as MongoDocumentsTab[];
|
||||||
let mongoDocumentsTab: MongoDocumentsTab = mongoDocumentsTabs && mongoDocumentsTabs[0];
|
let mongoDocumentsTab: MongoDocumentsTab = mongoDocumentsTabs && mongoDocumentsTabs[0];
|
||||||
|
|
||||||
if (mongoDocumentsTab) {
|
if (mongoDocumentsTab) {
|
||||||
this.container.tabsManager.activateTab(mongoDocumentsTab);
|
useTabs.getState().activateTab(mongoDocumentsTab);
|
||||||
} else {
|
} else {
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||||
databaseName: this.databaseId,
|
databaseName: this.databaseId,
|
||||||
@ -510,7 +523,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
node: this,
|
node: this,
|
||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
});
|
});
|
||||||
this.container.tabsManager.activateNewTab(mongoDocumentsTab);
|
useTabs.getState().activateNewTab(mongoDocumentsTab);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -525,13 +538,13 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const tab of this.container.tabsManager.openedTabs()) {
|
for (const tab of useTabs.getState().openedTabs) {
|
||||||
if (
|
if (
|
||||||
tab instanceof SchemaAnalyzerTab &&
|
tab instanceof SchemaAnalyzerTab &&
|
||||||
tab.collection?.databaseId === this.databaseId &&
|
tab.collection?.databaseId === this.databaseId &&
|
||||||
tab.collection?.id() === this.id()
|
tab.collection?.id() === this.id()
|
||||||
) {
|
) {
|
||||||
return this.container.tabsManager.activateTab(tab);
|
return useTabs.getState().activateTab(tab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -542,7 +555,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
tabTitle: "Schema",
|
tabTitle: "Schema",
|
||||||
});
|
});
|
||||||
this.documentIds([]);
|
this.documentIds([]);
|
||||||
this.container.tabsManager.activateNewTab(
|
useTabs.getState().activateNewTab(
|
||||||
new SchemaAnalyzerTab({
|
new SchemaAnalyzerTab({
|
||||||
account: userContext.databaseAccount,
|
account: userContext.databaseAccount,
|
||||||
masterKey: userContext.masterKey || "",
|
masterKey: userContext.masterKey || "",
|
||||||
@ -571,12 +584,9 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
||||||
const matchingTabs = this.container.tabsManager.getTabs(
|
const matchingTabs = useTabs.getState().getTabs(ViewModels.CollectionTabKind.CollectionSettingsV2, (tab) => {
|
||||||
ViewModels.CollectionTabKind.CollectionSettingsV2,
|
|
||||||
(tab) => {
|
|
||||||
return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
|
return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const traceStartData = {
|
const traceStartData = {
|
||||||
databaseName: this.databaseId,
|
databaseName: this.databaseId,
|
||||||
@ -608,15 +618,15 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
settingsTabOptions.onLoadStartKey = startKey;
|
settingsTabOptions.onLoadStartKey = startKey;
|
||||||
settingsTabOptions.tabKind = ViewModels.CollectionTabKind.CollectionSettingsV2;
|
settingsTabOptions.tabKind = ViewModels.CollectionTabKind.CollectionSettingsV2;
|
||||||
settingsTabV2 = new CollectionSettingsTabV2(settingsTabOptions);
|
settingsTabV2 = new CollectionSettingsTabV2(settingsTabOptions);
|
||||||
this.container.tabsManager.activateNewTab(settingsTabV2);
|
useTabs.getState().activateNewTab(settingsTabV2);
|
||||||
} else {
|
} else {
|
||||||
this.container.tabsManager.activateTab(settingsTabV2);
|
useTabs.getState().activateTab(settingsTabV2);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
||||||
const collection: ViewModels.Collection = source.collection || source;
|
const collection: ViewModels.Collection = source.collection || source;
|
||||||
const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
||||||
const title = "Query " + id;
|
const title = "Query " + id;
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||||
databaseName: this.databaseId,
|
databaseName: this.databaseId,
|
||||||
@ -626,7 +636,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
tabTitle: title,
|
tabTitle: title,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.container.tabsManager.activateNewTab(
|
useTabs.getState().activateNewTab(
|
||||||
new NewQueryTab(
|
new NewQueryTab(
|
||||||
{
|
{
|
||||||
tabKind: ViewModels.CollectionTabKind.Query,
|
tabKind: ViewModels.CollectionTabKind.Query,
|
||||||
@ -645,7 +655,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
|
|
||||||
public onNewMongoQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
public onNewMongoQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
||||||
const collection: ViewModels.Collection = source.collection || source;
|
const collection: ViewModels.Collection = source.collection || source;
|
||||||
const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
||||||
|
|
||||||
const title = "Query " + id;
|
const title = "Query " + id;
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||||
@ -672,11 +682,11 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.container.tabsManager.activateNewTab(newMongoQueryTab);
|
useTabs.getState().activateNewTab(newMongoQueryTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onNewGraphClick() {
|
public onNewGraphClick() {
|
||||||
const id: number = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Graph).length + 1;
|
const id: number = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Graph).length + 1;
|
||||||
const title: string = "Graph Query " + id;
|
const title: string = "Graph Query " + id;
|
||||||
|
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||||
@ -702,13 +712,11 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.container.tabsManager.activateNewTab(graphTab);
|
useTabs.getState().activateNewTab(graphTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onNewMongoShellClick() {
|
public onNewMongoShellClick() {
|
||||||
const mongoShellTabs = this.container.tabsManager.getTabs(
|
const mongoShellTabs = useTabs.getState().getTabs(ViewModels.CollectionTabKind.MongoShell) as NewMongoShellTab[];
|
||||||
ViewModels.CollectionTabKind.MongoShell
|
|
||||||
) as NewMongoShellTab[];
|
|
||||||
|
|
||||||
let index = 1;
|
let index = 1;
|
||||||
if (mongoShellTabs.length > 0) {
|
if (mongoShellTabs.length > 0) {
|
||||||
@ -729,7 +737,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.container.tabsManager.activateNewTab(mongoShellTab);
|
useTabs.getState().activateNewTab(mongoShellTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onNewStoredProcedureClick(source: ViewModels.Collection, event: MouseEvent) {
|
public onNewStoredProcedureClick(source: ViewModels.Collection, event: MouseEvent) {
|
||||||
@ -787,7 +795,9 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
} else {
|
} else {
|
||||||
this.expandStoredProcedures();
|
this.expandStoredProcedures();
|
||||||
}
|
}
|
||||||
this.container.tabsManager.refreshActiveTab(
|
useTabs
|
||||||
|
.getState()
|
||||||
|
.refreshActiveTab(
|
||||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -846,7 +856,9 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
} else {
|
} else {
|
||||||
this.expandUserDefinedFunctions();
|
this.expandUserDefinedFunctions();
|
||||||
}
|
}
|
||||||
this.container.tabsManager.refreshActiveTab(
|
useTabs
|
||||||
|
.getState()
|
||||||
|
.refreshActiveTab(
|
||||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -905,7 +917,9 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
} else {
|
} else {
|
||||||
this.expandTriggers();
|
this.expandTriggers();
|
||||||
}
|
}
|
||||||
this.container.tabsManager.refreshActiveTab(
|
useTabs
|
||||||
|
.getState()
|
||||||
|
.refreshActiveTab(
|
||||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as ko from "knockout";
|
|
||||||
import { HttpStatusCodes } from "../../Common/Constants";
|
import { HttpStatusCodes } from "../../Common/Constants";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { JunoClient } from "../../Juno/JunoClient";
|
import { JunoClient } from "../../Juno/JunoClient";
|
||||||
|
import { Features } from "../../Platform/Hosted/extractFeatures";
|
||||||
import { updateUserContext, userContext } from "../../UserContext";
|
import { updateUserContext, userContext } from "../../UserContext";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import Database from "./Database";
|
import Database from "./Database";
|
||||||
@ -35,7 +35,6 @@ describe("Add Schema", () => {
|
|||||||
collection.analyticalStorageTtl = undefined;
|
collection.analyticalStorageTtl = undefined;
|
||||||
const database = new Database(createMockContainer(), collection);
|
const database = new Database(createMockContainer(), collection);
|
||||||
database.container = createMockContainer();
|
database.container = createMockContainer();
|
||||||
database.container.isSchemaEnabled = ko.computed<boolean>(() => false);
|
|
||||||
|
|
||||||
database.junoClient = new JunoClient();
|
database.junoClient = new JunoClient();
|
||||||
database.junoClient.requestSchema = jest.fn();
|
database.junoClient.requestSchema = jest.fn();
|
||||||
@ -52,7 +51,11 @@ describe("Add Schema", () => {
|
|||||||
|
|
||||||
const database = new Database(createMockContainer(), collection);
|
const database = new Database(createMockContainer(), collection);
|
||||||
database.container = createMockContainer();
|
database.container = createMockContainer();
|
||||||
database.container.isSchemaEnabled = ko.computed<boolean>(() => true);
|
updateUserContext({
|
||||||
|
features: {
|
||||||
|
enableSchema: true,
|
||||||
|
} as Features,
|
||||||
|
});
|
||||||
|
|
||||||
database.junoClient = new JunoClient();
|
database.junoClient = new JunoClient();
|
||||||
database.junoClient.requestSchema = jest.fn();
|
database.junoClient.requestSchema = jest.fn();
|
||||||
|
@ -11,6 +11,7 @@ import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
|||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { useSidePanel } from "../../hooks/useSidePanel";
|
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||||
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
import { IJunoResponse, JunoClient } from "../../Juno/JunoClient";
|
import { IJunoResponse, JunoClient } from "../../Juno/JunoClient";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
@ -67,7 +68,7 @@ export default class Database implements ViewModels.Database {
|
|||||||
|
|
||||||
const pendingNotificationsPromise: Promise<DataModels.Notification> = this.getPendingThroughputSplitNotification();
|
const pendingNotificationsPromise: Promise<DataModels.Notification> = this.getPendingThroughputSplitNotification();
|
||||||
const tabKind = ViewModels.CollectionTabKind.DatabaseSettingsV2;
|
const tabKind = ViewModels.CollectionTabKind.DatabaseSettingsV2;
|
||||||
const matchingTabs = this.container.tabsManager.getTabs(tabKind, (tab) => tab.node?.id() === this.id());
|
const matchingTabs = useTabs.getState().getTabs(tabKind, (tab) => tab.node?.id() === this.id());
|
||||||
let settingsTab = matchingTabs?.[0] as DatabaseSettingsTabV2;
|
let settingsTab = matchingTabs?.[0] as DatabaseSettingsTabV2;
|
||||||
|
|
||||||
if (!settingsTab) {
|
if (!settingsTab) {
|
||||||
@ -91,7 +92,7 @@ export default class Database implements ViewModels.Database {
|
|||||||
};
|
};
|
||||||
settingsTab = new DatabaseSettingsTabV2(tabOptions);
|
settingsTab = new DatabaseSettingsTabV2(tabOptions);
|
||||||
settingsTab.pendingNotification(pendingNotification);
|
settingsTab.pendingNotification(pendingNotification);
|
||||||
this.container.tabsManager.activateNewTab(settingsTab);
|
useTabs.getState().activateNewTab(settingsTab);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
const errorMessage = getErrorMessage(error);
|
const errorMessage = getErrorMessage(error);
|
||||||
@ -116,11 +117,11 @@ export default class Database implements ViewModels.Database {
|
|||||||
pendingNotificationsPromise.then(
|
pendingNotificationsPromise.then(
|
||||||
(pendingNotification: DataModels.Notification) => {
|
(pendingNotification: DataModels.Notification) => {
|
||||||
settingsTab.pendingNotification(pendingNotification);
|
settingsTab.pendingNotification(pendingNotification);
|
||||||
this.container.tabsManager.activateTab(settingsTab);
|
useTabs.getState().activateTab(settingsTab);
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
settingsTab.pendingNotification(undefined);
|
settingsTab.pendingNotification(undefined);
|
||||||
this.container.tabsManager.activateTab(settingsTab);
|
useTabs.getState().activateTab(settingsTab);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -312,7 +313,7 @@ export default class Database implements ViewModels.Database {
|
|||||||
let checkForSchema: NodeJS.Timeout;
|
let checkForSchema: NodeJS.Timeout;
|
||||||
interval = interval || 5000;
|
interval = interval || 5000;
|
||||||
|
|
||||||
if (collection.analyticalStorageTtl !== undefined && this.container.isSchemaEnabled()) {
|
if (collection.analyticalStorageTtl !== undefined && userContext.features.enableSchema) {
|
||||||
collection.requestSchema = () => {
|
collection.requestSchema = () => {
|
||||||
this.junoClient.requestSchema({
|
this.junoClient.requestSchema({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
|
@ -2,6 +2,7 @@ import * as ko from "knockout";
|
|||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
@ -77,7 +78,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
|||||||
|
|
||||||
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
||||||
const collection: ViewModels.Collection = source.collection || source;
|
const collection: ViewModels.Collection = source.collection || source;
|
||||||
const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
||||||
const title = "Query " + id;
|
const title = "Query " + id;
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||||
databaseName: this.databaseId,
|
databaseName: this.databaseId,
|
||||||
@ -87,7 +88,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
|||||||
tabTitle: title,
|
tabTitle: title,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.container.tabsManager.activateNewTab(
|
useTabs.getState().activateNewTab(
|
||||||
new NewQueryTab(
|
new NewQueryTab(
|
||||||
{
|
{
|
||||||
tabKind: ViewModels.CollectionTabKind.Query,
|
tabKind: ViewModels.CollectionTabKind.Query,
|
||||||
@ -115,7 +116,9 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
|||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
|
|
||||||
const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
|
const documentsTabs: DocumentsTab[] = useTabs
|
||||||
|
.getState()
|
||||||
|
.getTabs(
|
||||||
ViewModels.CollectionTabKind.Documents,
|
ViewModels.CollectionTabKind.Documents,
|
||||||
(tab: TabsBase) =>
|
(tab: TabsBase) =>
|
||||||
tab.collection?.id() === this.id() &&
|
tab.collection?.id() === this.id() &&
|
||||||
@ -124,7 +127,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
|||||||
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
|
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
|
||||||
|
|
||||||
if (documentsTab) {
|
if (documentsTab) {
|
||||||
this.container.tabsManager.activateTab(documentsTab);
|
useTabs.getState().activateTab(documentsTab);
|
||||||
} else {
|
} else {
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||||
databaseName: this.databaseId,
|
databaseName: this.databaseId,
|
||||||
@ -146,7 +149,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
|||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.container.tabsManager.activateNewTab(documentsTab);
|
useTabs.getState().activateNewTab(documentsTab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,28 +1,34 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
import TabsBase from "../Tabs/TabsBase";
|
import TabsBase from "../Tabs/TabsBase";
|
||||||
import { useSelectedNode } from "../useSelectedNode";
|
import { useSelectedNode } from "../useSelectedNode";
|
||||||
|
|
||||||
describe("useSelectedNode.getState()", () => {
|
describe("useSelectedNode", () => {
|
||||||
const mockTab = {
|
const mockTab = {
|
||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||||
} as TabsBase;
|
} as TabsBase;
|
||||||
|
|
||||||
// TODO isDataNodeSelected needs a better design and refactor, but for now, we protect some of the code paths
|
// TODO isDataNodeSelected needs a better design and refactor, but for now, we protect some of the code paths
|
||||||
describe("isDataNodeSelected", () => {
|
describe("isDataNodeSelected", () => {
|
||||||
afterEach(() => useSelectedNode.getState().setSelectedNode(undefined));
|
afterEach(() => {
|
||||||
|
useSelectedNode.getState().setSelectedNode(undefined);
|
||||||
|
useTabs.setState({ activeTab: undefined });
|
||||||
|
});
|
||||||
it("it should not select if no selected node", () => {
|
it("it should not select if no selected node", () => {
|
||||||
const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected(mockTab, "foo", "bar", undefined);
|
useTabs.setState({ activeTab: mockTab });
|
||||||
|
const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected("foo", "bar", undefined);
|
||||||
expect(isDataNodeSelected).toBeFalsy();
|
expect(isDataNodeSelected).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("it should not select incorrect subnodekinds", () => {
|
it("it should not select incorrect subnodekinds", () => {
|
||||||
|
useTabs.setState({ activeTab: mockTab });
|
||||||
useSelectedNode.getState().setSelectedNode({
|
useSelectedNode.getState().setSelectedNode({
|
||||||
nodeKind: "nodeKind",
|
nodeKind: "nodeKind",
|
||||||
rid: "rid",
|
rid: "rid",
|
||||||
id: ko.observable<string>("id"),
|
id: ko.observable<string>("id"),
|
||||||
});
|
});
|
||||||
const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected(mockTab, "foo", "bar", undefined);
|
const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected("foo", "bar", undefined);
|
||||||
expect(isDataNodeSelected).toBeFalsy();
|
expect(isDataNodeSelected).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -32,11 +38,12 @@ describe("useSelectedNode.getState()", () => {
|
|||||||
rid: "rid",
|
rid: "rid",
|
||||||
id: ko.observable<string>("id"),
|
id: ko.observable<string>("id"),
|
||||||
});
|
});
|
||||||
const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected(undefined, "foo", "bar", undefined);
|
const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected("foo", "bar", undefined);
|
||||||
expect(isDataNodeSelected).toBeFalsy();
|
expect(isDataNodeSelected).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should select if correct database node regardless of subnodekinds", () => {
|
it("should select if correct database node regardless of subnodekinds", () => {
|
||||||
|
useTabs.setState({ activeTab: mockTab });
|
||||||
const subNodeKind = ViewModels.CollectionTabKind.Documents;
|
const subNodeKind = ViewModels.CollectionTabKind.Documents;
|
||||||
useSelectedNode.getState().setSelectedNode({
|
useSelectedNode.getState().setSelectedNode({
|
||||||
nodeKind: "Database",
|
nodeKind: "Database",
|
||||||
@ -46,7 +53,7 @@ describe("useSelectedNode.getState()", () => {
|
|||||||
} as ViewModels.TreeNode);
|
} as ViewModels.TreeNode);
|
||||||
const isDataNodeSelected = useSelectedNode
|
const isDataNodeSelected = useSelectedNode
|
||||||
.getState()
|
.getState()
|
||||||
.isDataNodeSelected(mockTab, "dbid", undefined, [ViewModels.CollectionTabKind.Documents]);
|
.isDataNodeSelected("dbid", undefined, [ViewModels.CollectionTabKind.Documents]);
|
||||||
expect(isDataNodeSelected).toBeTruthy();
|
expect(isDataNodeSelected).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -55,6 +62,7 @@ describe("useSelectedNode.getState()", () => {
|
|||||||
let activeTab = {
|
let activeTab = {
|
||||||
tabKind: subNodeKind,
|
tabKind: subNodeKind,
|
||||||
} as TabsBase;
|
} as TabsBase;
|
||||||
|
useTabs.setState({ activeTab });
|
||||||
useSelectedNode.getState().setSelectedNode({
|
useSelectedNode.getState().setSelectedNode({
|
||||||
nodeKind: "Collection",
|
nodeKind: "Collection",
|
||||||
rid: "collrid",
|
rid: "collrid",
|
||||||
@ -62,15 +70,14 @@ describe("useSelectedNode.getState()", () => {
|
|||||||
id: ko.observable<string>("collid"),
|
id: ko.observable<string>("collid"),
|
||||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind),
|
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind),
|
||||||
} as ViewModels.TreeNode);
|
} as ViewModels.TreeNode);
|
||||||
let isDataNodeSelected = useSelectedNode
|
let isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected("dbid", "collid", [subNodeKind]);
|
||||||
.getState()
|
|
||||||
.isDataNodeSelected(activeTab, "dbid", "collid", [subNodeKind]);
|
|
||||||
expect(isDataNodeSelected).toBeTruthy();
|
expect(isDataNodeSelected).toBeTruthy();
|
||||||
|
|
||||||
subNodeKind = ViewModels.CollectionTabKind.Graph;
|
subNodeKind = ViewModels.CollectionTabKind.Graph;
|
||||||
activeTab = {
|
activeTab = {
|
||||||
tabKind: subNodeKind,
|
tabKind: subNodeKind,
|
||||||
} as TabsBase;
|
} as TabsBase;
|
||||||
|
useTabs.setState({ activeTab });
|
||||||
useSelectedNode.getState().setSelectedNode({
|
useSelectedNode.getState().setSelectedNode({
|
||||||
nodeKind: "Collection",
|
nodeKind: "Collection",
|
||||||
rid: "collrid",
|
rid: "collrid",
|
||||||
@ -78,7 +85,7 @@ describe("useSelectedNode.getState()", () => {
|
|||||||
id: ko.observable<string>("collid"),
|
id: ko.observable<string>("collid"),
|
||||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind),
|
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind),
|
||||||
} as ViewModels.TreeNode);
|
} as ViewModels.TreeNode);
|
||||||
isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected(activeTab, "dbid", "collid", [subNodeKind]);
|
isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected("dbid", "collid", [subNodeKind]);
|
||||||
expect(isDataNodeSelected).toBeTruthy();
|
expect(isDataNodeSelected).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -93,9 +100,10 @@ describe("useSelectedNode.getState()", () => {
|
|||||||
const activeTab = {
|
const activeTab = {
|
||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||||
} as TabsBase;
|
} as TabsBase;
|
||||||
|
useTabs.setState({ activeTab });
|
||||||
const isDataNodeSelected = useSelectedNode
|
const isDataNodeSelected = useSelectedNode
|
||||||
.getState()
|
.getState()
|
||||||
.isDataNodeSelected(activeTab, "dbid", "collid", [ViewModels.CollectionTabKind.Settings]);
|
.isDataNodeSelected("dbid", "collid", [ViewModels.CollectionTabKind.Settings]);
|
||||||
expect(isDataNodeSelected).toBeFalsy();
|
expect(isDataNodeSelected).toBeFalsy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -16,6 +16,7 @@ import { Areas } from "../../Common/Constants";
|
|||||||
import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility";
|
import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
import { IPinnedRepo } from "../../Juno/JunoClient";
|
import { IPinnedRepo } from "../../Juno/JunoClient";
|
||||||
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
|
||||||
import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
@ -57,7 +58,10 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
this.parameters = ko.observable(Date.now());
|
this.parameters = ko.observable(Date.now());
|
||||||
|
|
||||||
useSelectedNode.subscribe(() => this.triggerRender());
|
useSelectedNode.subscribe(() => this.triggerRender());
|
||||||
this.container.tabsManager.activeTab.subscribe((newValue: TabsBase) => this.triggerRender());
|
useTabs.subscribe(
|
||||||
|
() => this.triggerRender(),
|
||||||
|
(state) => state.activeTab
|
||||||
|
);
|
||||||
useNotebook.subscribe(
|
useNotebook.subscribe(
|
||||||
() => this.triggerRender(),
|
() => this.triggerRender(),
|
||||||
(state) => state.isNotebookEnabled
|
(state) => state.isNotebookEnabled
|
||||||
@ -188,8 +192,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
isExpanded: false,
|
isExpanded: false,
|
||||||
className: "databaseHeader",
|
className: "databaseHeader",
|
||||||
children: [],
|
children: [],
|
||||||
isSelected: () =>
|
isSelected: () => useSelectedNode.getState().isDataNodeSelected(database.id()),
|
||||||
useSelectedNode.getState().isDataNodeSelected(this.container.tabsManager.activeTab(), database.id()),
|
|
||||||
contextMenu: ResourceTreeContextMenuButtonFactory.createDatabaseContextMenu(this.container, database.id()),
|
contextMenu: ResourceTreeContextMenuButtonFactory.createDatabaseContextMenu(this.container, database.id()),
|
||||||
onClick: async (isExpanded) => {
|
onClick: async (isExpanded) => {
|
||||||
// Rewritten version of expandCollapseDatabase():
|
// Rewritten version of expandCollapseDatabase():
|
||||||
@ -204,7 +207,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
databaseNode.isLoading = false;
|
databaseNode.isLoading = false;
|
||||||
useSelectedNode.getState().setSelectedNode(database);
|
useSelectedNode.getState().setSelectedNode(database);
|
||||||
useCommandBar.getState().setContextButtons([]);
|
useCommandBar.getState().setContextButtons([]);
|
||||||
this.container.tabsManager.refreshActiveTab((tab: TabsBase) => tab.collection?.databaseId === database.id());
|
useTabs.getState().refreshActiveTab((tab: TabsBase) => tab.collection?.databaseId === database.id());
|
||||||
},
|
},
|
||||||
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(database),
|
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(database),
|
||||||
};
|
};
|
||||||
@ -215,9 +218,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
isSelected: () =>
|
isSelected: () =>
|
||||||
useSelectedNode
|
useSelectedNode
|
||||||
.getState()
|
.getState()
|
||||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), database.id(), undefined, [
|
.isDataNodeSelected(database.id(), undefined, [ViewModels.CollectionTabKind.DatabaseSettings]),
|
||||||
ViewModels.CollectionTabKind.DatabaseSettings,
|
|
||||||
]),
|
|
||||||
onClick: database.onSettingsClick.bind(database),
|
onClick: database.onSettingsClick.bind(database),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -265,7 +266,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
isSelected: () =>
|
isSelected: () =>
|
||||||
useSelectedNode
|
useSelectedNode
|
||||||
.getState()
|
.getState()
|
||||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
|
.isDataNodeSelected(collection.databaseId, collection.id(), [
|
||||||
ViewModels.CollectionTabKind.Documents,
|
ViewModels.CollectionTabKind.Documents,
|
||||||
ViewModels.CollectionTabKind.Graph,
|
ViewModels.CollectionTabKind.Graph,
|
||||||
]),
|
]),
|
||||||
@ -283,9 +284,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
isSelected: () =>
|
isSelected: () =>
|
||||||
useSelectedNode
|
useSelectedNode
|
||||||
.getState()
|
.getState()
|
||||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
|
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.SchemaAnalyzer]),
|
||||||
ViewModels.CollectionTabKind.SchemaAnalyzer,
|
|
||||||
]),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,9 +295,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
isSelected: () =>
|
isSelected: () =>
|
||||||
useSelectedNode
|
useSelectedNode
|
||||||
.getState()
|
.getState()
|
||||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
|
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Settings]),
|
||||||
ViewModels.CollectionTabKind.Settings,
|
|
||||||
]),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,9 +323,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
isSelected: () =>
|
isSelected: () =>
|
||||||
useSelectedNode
|
useSelectedNode
|
||||||
.getState()
|
.getState()
|
||||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
|
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Conflicts]),
|
||||||
ViewModels.CollectionTabKind.Conflicts,
|
|
||||||
]),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,7 +338,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
// Rewritten version of expandCollapseCollection
|
// Rewritten version of expandCollapseCollection
|
||||||
useSelectedNode.getState().setSelectedNode(collection);
|
useSelectedNode.getState().setSelectedNode(collection);
|
||||||
useCommandBar.getState().setContextButtons([]);
|
useCommandBar.getState().setContextButtons([]);
|
||||||
this.container.tabsManager.refreshActiveTab(
|
useTabs
|
||||||
|
.getState()
|
||||||
|
.refreshActiveTab(
|
||||||
(tab: TabsBase) =>
|
(tab: TabsBase) =>
|
||||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||||
);
|
);
|
||||||
@ -355,10 +352,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
collection.loadTriggers();
|
collection.loadTriggers();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isSelected: () =>
|
isSelected: () => useSelectedNode.getState().isDataNodeSelected(collection.databaseId, collection.id()),
|
||||||
useSelectedNode
|
|
||||||
.getState()
|
|
||||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id()),
|
|
||||||
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(collection),
|
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(collection),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -372,14 +366,16 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
isSelected: () =>
|
isSelected: () =>
|
||||||
useSelectedNode
|
useSelectedNode
|
||||||
.getState()
|
.getState()
|
||||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
|
.isDataNodeSelected(collection.databaseId, collection.id(), [
|
||||||
ViewModels.CollectionTabKind.StoredProcedures,
|
ViewModels.CollectionTabKind.StoredProcedures,
|
||||||
]),
|
]),
|
||||||
contextMenu: ResourceTreeContextMenuButtonFactory.createStoreProcedureContextMenuItems(this.container, sp),
|
contextMenu: ResourceTreeContextMenuButtonFactory.createStoreProcedureContextMenuItems(this.container, sp),
|
||||||
})),
|
})),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.StoredProcedures);
|
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.StoredProcedures);
|
||||||
this.container.tabsManager.refreshActiveTab(
|
useTabs
|
||||||
|
.getState()
|
||||||
|
.refreshActiveTab(
|
||||||
(tab: TabsBase) =>
|
(tab: TabsBase) =>
|
||||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||||
);
|
);
|
||||||
@ -396,7 +392,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
isSelected: () =>
|
isSelected: () =>
|
||||||
useSelectedNode
|
useSelectedNode
|
||||||
.getState()
|
.getState()
|
||||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
|
.isDataNodeSelected(collection.databaseId, collection.id(), [
|
||||||
ViewModels.CollectionTabKind.UserDefinedFunctions,
|
ViewModels.CollectionTabKind.UserDefinedFunctions,
|
||||||
]),
|
]),
|
||||||
contextMenu: ResourceTreeContextMenuButtonFactory.createUserDefinedFunctionContextMenuItems(
|
contextMenu: ResourceTreeContextMenuButtonFactory.createUserDefinedFunctionContextMenuItems(
|
||||||
@ -406,7 +402,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
})),
|
})),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.UserDefinedFunctions);
|
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.UserDefinedFunctions);
|
||||||
this.container.tabsManager.refreshActiveTab(
|
useTabs
|
||||||
|
.getState()
|
||||||
|
.refreshActiveTab(
|
||||||
(tab: TabsBase) =>
|
(tab: TabsBase) =>
|
||||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||||
);
|
);
|
||||||
@ -423,14 +421,14 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
isSelected: () =>
|
isSelected: () =>
|
||||||
useSelectedNode
|
useSelectedNode
|
||||||
.getState()
|
.getState()
|
||||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
|
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Triggers]),
|
||||||
ViewModels.CollectionTabKind.Triggers,
|
|
||||||
]),
|
|
||||||
contextMenu: ResourceTreeContextMenuButtonFactory.createTriggerContextMenuItems(this.container, trigger),
|
contextMenu: ResourceTreeContextMenuButtonFactory.createTriggerContextMenuItems(this.container, trigger),
|
||||||
})),
|
})),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Triggers);
|
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Triggers);
|
||||||
this.container.tabsManager.refreshActiveTab(
|
useTabs
|
||||||
|
.getState()
|
||||||
|
.refreshActiveTab(
|
||||||
(tab: TabsBase) =>
|
(tab: TabsBase) =>
|
||||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||||
);
|
);
|
||||||
@ -452,9 +450,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
children: this.getSchemaNodes(collection.schema.fields),
|
children: this.getSchemaNodes(collection.schema.fields),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Schema);
|
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Schema);
|
||||||
this.container.tabsManager.refreshActiveTab(
|
useTabs.getState().refreshActiveTab((tab: TabsBase) => tab.collection && tab.collection.rid === collection.rid);
|
||||||
(tab: TabsBase) => tab.collection && tab.collection.rid === collection.rid
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -584,7 +580,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
className: "notebookHeader galleryHeader",
|
className: "notebookHeader galleryHeader",
|
||||||
onClick: () => this.container.openGallery(),
|
onClick: () => this.container.openGallery(),
|
||||||
isSelected: () => {
|
isSelected: () => {
|
||||||
const activeTab = this.container.tabsManager.activeTab();
|
const activeTab = useTabs.getState().activeTab;
|
||||||
return activeTab && activeTab.tabKind === ViewModels.CollectionTabKind.Gallery;
|
return activeTab && activeTab.tabKind === ViewModels.CollectionTabKind.Gallery;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -678,7 +674,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
className: "notebookHeader",
|
className: "notebookHeader",
|
||||||
onClick: () => onFileClick(item),
|
onClick: () => onFileClick(item),
|
||||||
isSelected: () => {
|
isSelected: () => {
|
||||||
const activeTab = this.container.tabsManager.activeTab();
|
const activeTab = useTabs.getState().activeTab;
|
||||||
return (
|
return (
|
||||||
activeTab &&
|
activeTab &&
|
||||||
activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
|
activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
|
||||||
@ -833,7 +829,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
isSelected: () => {
|
isSelected: () => {
|
||||||
const activeTab = this.container.tabsManager.activeTab();
|
const activeTab = useTabs.getState().activeTab;
|
||||||
return (
|
return (
|
||||||
activeTab &&
|
activeTab &&
|
||||||
activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
|
activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
|
||||||
|
@ -3,6 +3,7 @@ import * as React from "react";
|
|||||||
import CollectionIcon from "../../../images/tree-collection.svg";
|
import CollectionIcon from "../../../images/tree-collection.svg";
|
||||||
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { TreeComponent, TreeNode } from "../Controls/TreeComponent/TreeComponent";
|
import { TreeComponent, TreeNode } from "../Controls/TreeComponent/TreeComponent";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
@ -24,7 +25,10 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
|
|||||||
(state) => state.resourceTokenCollection
|
(state) => state.resourceTokenCollection
|
||||||
);
|
);
|
||||||
useSelectedNode.subscribe(() => this.triggerRender());
|
useSelectedNode.subscribe(() => this.triggerRender());
|
||||||
this.container.tabsManager && this.container.tabsManager.activeTab.subscribe(() => this.triggerRender());
|
useTabs.subscribe(
|
||||||
|
() => this.triggerRender(),
|
||||||
|
(state) => state.activeTab
|
||||||
|
);
|
||||||
|
|
||||||
this.triggerRender();
|
this.triggerRender();
|
||||||
}
|
}
|
||||||
@ -55,9 +59,7 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
|
|||||||
isSelected: () =>
|
isSelected: () =>
|
||||||
useSelectedNode
|
useSelectedNode
|
||||||
.getState()
|
.getState()
|
||||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id(), [
|
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Documents]),
|
||||||
ViewModels.CollectionTabKind.Documents,
|
|
||||||
]),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const collectionNode: TreeNode = {
|
const collectionNode: TreeNode = {
|
||||||
@ -70,14 +72,13 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
|
|||||||
// Rewritten version of expandCollapseCollection
|
// Rewritten version of expandCollapseCollection
|
||||||
useSelectedNode.getState().setSelectedNode(collection);
|
useSelectedNode.getState().setSelectedNode(collection);
|
||||||
useCommandBar.getState().setContextButtons([]);
|
useCommandBar.getState().setContextButtons([]);
|
||||||
this.container.tabsManager.refreshActiveTab(
|
useTabs
|
||||||
|
.getState()
|
||||||
|
.refreshActiveTab(
|
||||||
(tab) => tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
(tab) => tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
isSelected: () =>
|
isSelected: () => useSelectedNode.getState().isDataNodeSelected(collection.databaseId, collection.id()),
|
||||||
useSelectedNode
|
|
||||||
.getState()
|
|
||||||
.isDataNodeSelected(this.container.tabsManager.activeTab(), collection.databaseId, collection.id()),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -4,6 +4,7 @@ import * as Constants from "../../Common/Constants";
|
|||||||
import { deleteStoredProcedure } from "../../Common/dataAccess/deleteStoredProcedure";
|
import { deleteStoredProcedure } from "../../Common/dataAccess/deleteStoredProcedure";
|
||||||
import { executeStoredProcedure } from "../../Common/dataAccess/executeStoredProcedure";
|
import { executeStoredProcedure } from "../../Common/dataAccess/executeStoredProcedure";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
@ -62,7 +63,7 @@ export default class StoredProcedure {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static create(source: ViewModels.Collection, event: MouseEvent) {
|
public static create(source: ViewModels.Collection, event: MouseEvent) {
|
||||||
const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.StoredProcedures).length + 1;
|
const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.StoredProcedures).length + 1;
|
||||||
const storedProcedure = <StoredProcedureDefinition>{
|
const storedProcedure = <StoredProcedureDefinition>{
|
||||||
id: "",
|
id: "",
|
||||||
body: sampleStoredProcedureBody,
|
body: sampleStoredProcedureBody,
|
||||||
@ -84,7 +85,7 @@ export default class StoredProcedure {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
source.container.tabsManager.activateNewTab(storedProcedureTab);
|
useTabs.getState().activateNewTab(storedProcedureTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
public select() {
|
public select() {
|
||||||
@ -99,14 +100,16 @@ export default class StoredProcedure {
|
|||||||
public open = () => {
|
public open = () => {
|
||||||
this.select();
|
this.select();
|
||||||
|
|
||||||
const storedProcedureTabs: NewStoredProcedureTab[] = this.container.tabsManager.getTabs(
|
const storedProcedureTabs: NewStoredProcedureTab[] = useTabs
|
||||||
|
.getState()
|
||||||
|
.getTabs(
|
||||||
ViewModels.CollectionTabKind.StoredProcedures,
|
ViewModels.CollectionTabKind.StoredProcedures,
|
||||||
(tab: TabsBase) => tab.node && tab.node.rid === this.rid
|
(tab: TabsBase) => tab.node && tab.node.rid === this.rid
|
||||||
) as NewStoredProcedureTab[];
|
) as NewStoredProcedureTab[];
|
||||||
let storedProcedureTab: NewStoredProcedureTab = storedProcedureTabs && storedProcedureTabs[0];
|
let storedProcedureTab: NewStoredProcedureTab = storedProcedureTabs && storedProcedureTabs[0];
|
||||||
|
|
||||||
if (storedProcedureTab) {
|
if (storedProcedureTab) {
|
||||||
this.container.tabsManager.activateTab(storedProcedureTab);
|
useTabs.getState().activateTab(storedProcedureTab);
|
||||||
} else {
|
} else {
|
||||||
const storedProcedureData = <StoredProcedureDefinition>{
|
const storedProcedureData = <StoredProcedureDefinition>{
|
||||||
_rid: this.rid,
|
_rid: this.rid,
|
||||||
@ -131,7 +134,7 @@ export default class StoredProcedure {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.container.tabsManager.activateNewTab(storedProcedureTab);
|
useTabs.getState().activateNewTab(storedProcedureTab);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
public delete() {
|
public delete() {
|
||||||
@ -141,7 +144,7 @@ export default class StoredProcedure {
|
|||||||
|
|
||||||
deleteStoredProcedure(this.collection.databaseId, this.collection.id(), this.id()).then(
|
deleteStoredProcedure(this.collection.databaseId, this.collection.id(), this.id()).then(
|
||||||
() => {
|
() => {
|
||||||
this.container.tabsManager.closeTabsByComparator((tab: TabsBase) => tab.node && tab.node.rid === this.rid);
|
useTabs.getState().closeTabsByComparator((tab: TabsBase) => tab.node && tab.node.rid === this.rid);
|
||||||
this.collection.children.remove(this);
|
this.collection.children.remove(this);
|
||||||
},
|
},
|
||||||
(reason) => {}
|
(reason) => {}
|
||||||
@ -149,7 +152,9 @@ export default class StoredProcedure {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public execute(params: string[], partitionKeyValue?: string): void {
|
public execute(params: string[], partitionKeyValue?: string): void {
|
||||||
const sprocTabs: NewStoredProcedureTab[] = this.container.tabsManager.getTabs(
|
const sprocTabs: NewStoredProcedureTab[] = useTabs
|
||||||
|
.getState()
|
||||||
|
.getTabs(
|
||||||
ViewModels.CollectionTabKind.StoredProcedures,
|
ViewModels.CollectionTabKind.StoredProcedures,
|
||||||
(tab: TabsBase) => tab.node && tab.node.rid === this.rid
|
(tab: TabsBase) => tab.node && tab.node.rid === this.rid
|
||||||
) as NewStoredProcedureTab[];
|
) as NewStoredProcedureTab[];
|
||||||
|
@ -3,6 +3,7 @@ import * as ko from "knockout";
|
|||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import { deleteTrigger } from "../../Common/dataAccess/deleteTrigger";
|
import { deleteTrigger } from "../../Common/dataAccess/deleteTrigger";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
@ -42,7 +43,7 @@ export default class Trigger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static create(source: ViewModels.Collection, event: MouseEvent) {
|
public static create(source: ViewModels.Collection, event: MouseEvent) {
|
||||||
const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Triggers).length + 1;
|
const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Triggers).length + 1;
|
||||||
const trigger = <StoredProcedureDefinition>{
|
const trigger = <StoredProcedureDefinition>{
|
||||||
id: "",
|
id: "",
|
||||||
body: "function trigger(){}",
|
body: "function trigger(){}",
|
||||||
@ -60,20 +61,19 @@ export default class Trigger {
|
|||||||
node: source,
|
node: source,
|
||||||
});
|
});
|
||||||
|
|
||||||
source.container.tabsManager.activateNewTab(triggerTab);
|
useTabs.getState().activateNewTab(triggerTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
public open = () => {
|
public open = () => {
|
||||||
this.select();
|
this.select();
|
||||||
|
|
||||||
const triggerTabs: TriggerTab[] = this.container.tabsManager.getTabs(
|
const triggerTabs: TriggerTab[] = useTabs
|
||||||
ViewModels.CollectionTabKind.Triggers,
|
.getState()
|
||||||
(tab) => tab.node && tab.node.rid === this.rid
|
.getTabs(ViewModels.CollectionTabKind.Triggers, (tab) => tab.node && tab.node.rid === this.rid) as TriggerTab[];
|
||||||
) as TriggerTab[];
|
|
||||||
let triggerTab: TriggerTab = triggerTabs && triggerTabs[0];
|
let triggerTab: TriggerTab = triggerTabs && triggerTabs[0];
|
||||||
|
|
||||||
if (triggerTab) {
|
if (triggerTab) {
|
||||||
this.container.tabsManager.activateTab(triggerTab);
|
useTabs.getState().activateTab(triggerTab);
|
||||||
} else {
|
} else {
|
||||||
const triggerData = <StoredProcedureDefinition>{
|
const triggerData = <StoredProcedureDefinition>{
|
||||||
_rid: this.rid,
|
_rid: this.rid,
|
||||||
@ -94,7 +94,7 @@ export default class Trigger {
|
|||||||
node: this,
|
node: this,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.container.tabsManager.activateNewTab(triggerTab);
|
useTabs.getState().activateNewTab(triggerTab);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ export default class Trigger {
|
|||||||
|
|
||||||
deleteTrigger(this.collection.databaseId, this.collection.id(), this.id()).then(
|
deleteTrigger(this.collection.databaseId, this.collection.id(), this.id()).then(
|
||||||
() => {
|
() => {
|
||||||
this.container.tabsManager.closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
|
useTabs.getState().closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
|
||||||
this.collection.children.remove(this);
|
this.collection.children.remove(this);
|
||||||
},
|
},
|
||||||
(reason) => {}
|
(reason) => {}
|
||||||
|
@ -3,6 +3,7 @@ import * as ko from "knockout";
|
|||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import { deleteUserDefinedFunction } from "../../Common/dataAccess/deleteUserDefinedFunction";
|
import { deleteUserDefinedFunction } from "../../Common/dataAccess/deleteUserDefinedFunction";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
@ -30,7 +31,7 @@ export default class UserDefinedFunction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static create(source: ViewModels.Collection, event: MouseEvent) {
|
public static create(source: ViewModels.Collection, event: MouseEvent) {
|
||||||
const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.UserDefinedFunctions).length + 1;
|
const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.UserDefinedFunctions).length + 1;
|
||||||
const userDefinedFunction = {
|
const userDefinedFunction = {
|
||||||
id: "",
|
id: "",
|
||||||
body: "function userDefinedFunction(){}",
|
body: "function userDefinedFunction(){}",
|
||||||
@ -46,20 +47,22 @@ export default class UserDefinedFunction {
|
|||||||
node: source,
|
node: source,
|
||||||
});
|
});
|
||||||
|
|
||||||
source.container.tabsManager.activateNewTab(userDefinedFunctionTab);
|
useTabs.getState().activateNewTab(userDefinedFunctionTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
public open = () => {
|
public open = () => {
|
||||||
this.select();
|
this.select();
|
||||||
|
|
||||||
const userDefinedFunctionTabs: UserDefinedFunctionTab[] = this.container.tabsManager.getTabs(
|
const userDefinedFunctionTabs: UserDefinedFunctionTab[] = useTabs
|
||||||
|
.getState()
|
||||||
|
.getTabs(
|
||||||
ViewModels.CollectionTabKind.UserDefinedFunctions,
|
ViewModels.CollectionTabKind.UserDefinedFunctions,
|
||||||
(tab) => tab.node?.rid === this.rid
|
(tab) => tab.node?.rid === this.rid
|
||||||
) as UserDefinedFunctionTab[];
|
) as UserDefinedFunctionTab[];
|
||||||
let userDefinedFunctionTab: UserDefinedFunctionTab = userDefinedFunctionTabs && userDefinedFunctionTabs[0];
|
let userDefinedFunctionTab: UserDefinedFunctionTab = userDefinedFunctionTabs && userDefinedFunctionTabs[0];
|
||||||
|
|
||||||
if (userDefinedFunctionTab) {
|
if (userDefinedFunctionTab) {
|
||||||
this.container.tabsManager.activateTab(userDefinedFunctionTab);
|
useTabs.getState().activateTab(userDefinedFunctionTab);
|
||||||
} else {
|
} else {
|
||||||
const userDefinedFunctionData = {
|
const userDefinedFunctionData = {
|
||||||
_rid: this.rid,
|
_rid: this.rid,
|
||||||
@ -78,7 +81,7 @@ export default class UserDefinedFunction {
|
|||||||
node: this,
|
node: this,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.container.tabsManager.activateNewTab(userDefinedFunctionTab);
|
useTabs.getState().activateNewTab(userDefinedFunctionTab);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -98,7 +101,7 @@ export default class UserDefinedFunction {
|
|||||||
|
|
||||||
deleteUserDefinedFunction(this.collection.databaseId, this.collection.id(), this.id()).then(
|
deleteUserDefinedFunction(this.collection.databaseId, this.collection.id(), this.id()).then(
|
||||||
() => {
|
() => {
|
||||||
this.container.tabsManager.closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
|
useTabs.getState().closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
|
||||||
this.collection.children.remove(this);
|
this.collection.children.remove(this);
|
||||||
},
|
},
|
||||||
(reason) => {}
|
(reason) => {}
|
||||||
|
@ -2,6 +2,7 @@ import _ from "underscore";
|
|||||||
import create, { UseStore } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import * as Constants from "../Common/Constants";
|
import * as Constants from "../Common/Constants";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
|
import { useSelectedNode } from "./useSelectedNode";
|
||||||
|
|
||||||
interface DatabasesState {
|
interface DatabasesState {
|
||||||
databases: ViewModels.Database[];
|
databases: ViewModels.Database[];
|
||||||
@ -17,6 +18,7 @@ interface DatabasesState {
|
|||||||
isLastCollection: () => boolean;
|
isLastCollection: () => boolean;
|
||||||
loadDatabaseOffers: () => Promise<void>;
|
loadDatabaseOffers: () => Promise<void>;
|
||||||
isFirstResourceCreated: () => boolean;
|
isFirstResourceCreated: () => boolean;
|
||||||
|
findSelectedDatabase: () => ViewModels.Database;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useDatabases: UseStore<DatabasesState> = create((set, get) => ({
|
export const useDatabases: UseStore<DatabasesState> = create((set, get) => ({
|
||||||
@ -112,4 +114,19 @@ export const useDatabases: UseStore<DatabasesState> = create((set, get) => ({
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
findSelectedDatabase: (): ViewModels.Database => {
|
||||||
|
const selectedNode = useSelectedNode.getState().selectedNode;
|
||||||
|
if (!selectedNode) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (selectedNode.nodeKind === "Database") {
|
||||||
|
return _.find(get().databases, (database: ViewModels.Database) => database.id() === selectedNode.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedNode.nodeKind === "Collection") {
|
||||||
|
return selectedNode.database;
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedNode.collection?.database;
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
import _ from "underscore";
|
|
||||||
import create, { UseStore } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import TabsBase from "./Tabs/TabsBase";
|
import { useTabs } from "../hooks/useTabs";
|
||||||
import { useDatabases } from "./useDatabases";
|
|
||||||
|
|
||||||
export interface SelectedNodeState {
|
export interface SelectedNodeState {
|
||||||
selectedNode: ViewModels.TreeNode;
|
selectedNode: ViewModels.TreeNode;
|
||||||
setSelectedNode: (node: ViewModels.TreeNode) => void;
|
setSelectedNode: (node: ViewModels.TreeNode) => void;
|
||||||
isDatabaseNodeOrNoneSelected: () => boolean;
|
isDatabaseNodeOrNoneSelected: () => boolean;
|
||||||
findSelectedDatabase: () => ViewModels.Database;
|
|
||||||
findSelectedCollection: () => ViewModels.Collection;
|
findSelectedCollection: () => ViewModels.Collection;
|
||||||
isDataNodeSelected: (
|
isDataNodeSelected: (
|
||||||
activeTab: TabsBase,
|
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId?: string,
|
collectionId?: string,
|
||||||
subnodeKinds?: ViewModels.CollectionTabKind[]
|
subnodeKinds?: ViewModels.CollectionTabKind[]
|
||||||
@ -25,30 +21,11 @@ export const useSelectedNode: UseStore<SelectedNodeState> = create((set, get) =>
|
|||||||
const selectedNode = get().selectedNode;
|
const selectedNode = get().selectedNode;
|
||||||
return !selectedNode || selectedNode.nodeKind === "Database";
|
return !selectedNode || selectedNode.nodeKind === "Database";
|
||||||
},
|
},
|
||||||
findSelectedDatabase: (): ViewModels.Database => {
|
|
||||||
const selectedNode = get().selectedNode;
|
|
||||||
if (!selectedNode) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (selectedNode.nodeKind === "Database") {
|
|
||||||
return _.find(
|
|
||||||
useDatabases.getState().databases,
|
|
||||||
(database: ViewModels.Database) => database.id() === selectedNode.id()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedNode.nodeKind === "Collection") {
|
|
||||||
return selectedNode.database;
|
|
||||||
}
|
|
||||||
|
|
||||||
return selectedNode.collection?.database;
|
|
||||||
},
|
|
||||||
findSelectedCollection: (): ViewModels.Collection => {
|
findSelectedCollection: (): ViewModels.Collection => {
|
||||||
const selectedNode = get().selectedNode;
|
const selectedNode = get().selectedNode;
|
||||||
return (selectedNode.nodeKind === "Collection" ? selectedNode : selectedNode.collection) as ViewModels.Collection;
|
return (selectedNode.nodeKind === "Collection" ? selectedNode : selectedNode.collection) as ViewModels.Collection;
|
||||||
},
|
},
|
||||||
isDataNodeSelected: (
|
isDataNodeSelected: (
|
||||||
activeTab: TabsBase,
|
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId?: string,
|
collectionId?: string,
|
||||||
subnodeKinds?: ViewModels.CollectionTabKind[]
|
subnodeKinds?: ViewModels.CollectionTabKind[]
|
||||||
@ -70,6 +47,7 @@ export const useSelectedNode: UseStore<SelectedNodeState> = create((set, get) =>
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const activeTab = useTabs.getState().activeTab;
|
||||||
const selectedSubnodeKind = collectionId
|
const selectedSubnodeKind = collectionId
|
||||||
? (selectedNode as ViewModels.Collection).selectedSubnodeKind()
|
? (selectedNode as ViewModels.Collection).selectedSubnodeKind()
|
||||||
: (selectedNode as ViewModels.Database).selectedSubnodeKind();
|
: (selectedNode as ViewModels.Database).selectedSubnodeKind();
|
||||||
|
13
src/Main.tsx
13
src/Main.tsx
@ -34,7 +34,6 @@ import "./Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.less";
|
|||||||
import "./Explorer/Controls/JsonEditor/JsonEditorComponent.less";
|
import "./Explorer/Controls/JsonEditor/JsonEditorComponent.less";
|
||||||
import "./Explorer/Controls/Notebook/NotebookTerminalComponent.less";
|
import "./Explorer/Controls/Notebook/NotebookTerminalComponent.less";
|
||||||
import "./Explorer/Controls/TreeComponent/treeComponent.less";
|
import "./Explorer/Controls/TreeComponent/treeComponent.less";
|
||||||
import { ExplorerParams } from "./Explorer/Explorer";
|
|
||||||
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
|
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
|
||||||
import "./Explorer/Menus/CommandBar/CommandBarComponent.less";
|
import "./Explorer/Menus/CommandBar/CommandBarComponent.less";
|
||||||
import { CommandBar } from "./Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
import { CommandBar } from "./Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
@ -56,14 +55,10 @@ initializeIcons();
|
|||||||
|
|
||||||
const App: React.FunctionComponent = () => {
|
const App: React.FunctionComponent = () => {
|
||||||
const [isLeftPaneExpanded, setIsLeftPaneExpanded] = useState<boolean>(true);
|
const [isLeftPaneExpanded, setIsLeftPaneExpanded] = useState<boolean>(true);
|
||||||
const { tabs, activeTab, tabsManager } = useTabs();
|
const openedTabs = useTabs((state) => state.openedTabs);
|
||||||
|
|
||||||
const explorerParams: ExplorerParams = {
|
|
||||||
tabsManager,
|
|
||||||
};
|
|
||||||
|
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
const explorer = useKnockoutExplorer(config?.platform, explorerParams);
|
const explorer = useKnockoutExplorer(config?.platform);
|
||||||
|
|
||||||
const toggleLeftPaneExpanded = () => {
|
const toggleLeftPaneExpanded = () => {
|
||||||
setIsLeftPaneExpanded(!isLeftPaneExpanded);
|
setIsLeftPaneExpanded(!isLeftPaneExpanded);
|
||||||
@ -100,8 +95,8 @@ const App: React.FunctionComponent = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Collections Tree - End */}
|
{/* Collections Tree - End */}
|
||||||
{tabs.length === 0 && <SplashScreen explorer={explorer} />}
|
{openedTabs.length === 0 && <SplashScreen explorer={explorer} />}
|
||||||
<Tabs tabs={tabs} activeTab={activeTab} />
|
<Tabs />
|
||||||
</div>
|
</div>
|
||||||
{/* Collections Tree and Tabs - End */}
|
{/* Collections Tree and Tabs - End */}
|
||||||
<div
|
<div
|
||||||
|
@ -8,7 +8,7 @@ import { configContext, Platform, updateConfigContext } from "../ConfigContext";
|
|||||||
import { ActionType, DataExplorerAction } from "../Contracts/ActionContracts";
|
import { ActionType, DataExplorerAction } from "../Contracts/ActionContracts";
|
||||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
import { DataExplorerInputsFrame } from "../Contracts/ViewModels";
|
import { DataExplorerInputsFrame } from "../Contracts/ViewModels";
|
||||||
import Explorer, { ExplorerParams } from "../Explorer/Explorer";
|
import Explorer from "../Explorer/Explorer";
|
||||||
import { handleOpenAction } from "../Explorer/OpenActions/OpenActions";
|
import { handleOpenAction } from "../Explorer/OpenActions/OpenActions";
|
||||||
import { useDatabases } from "../Explorer/useDatabases";
|
import { useDatabases } from "../Explorer/useDatabases";
|
||||||
import {
|
import {
|
||||||
@ -37,20 +37,20 @@ import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation";
|
|||||||
// This hook has a LOT of magic, but ideally we can delete it once we have removed KO and switched entirely to React
|
// This hook has a LOT of magic, but ideally we can delete it once we have removed KO and switched entirely to React
|
||||||
// Please tread carefully :)
|
// Please tread carefully :)
|
||||||
|
|
||||||
export function useKnockoutExplorer(platform: Platform, explorerParams: ExplorerParams): Explorer {
|
export function useKnockoutExplorer(platform: Platform): Explorer {
|
||||||
const [explorer, setExplorer] = useState<Explorer>();
|
const [explorer, setExplorer] = useState<Explorer>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const effect = async () => {
|
const effect = async () => {
|
||||||
if (platform) {
|
if (platform) {
|
||||||
if (platform === Platform.Hosted) {
|
if (platform === Platform.Hosted) {
|
||||||
const explorer = await configureHosted(explorerParams);
|
const explorer = await configureHosted();
|
||||||
setExplorer(explorer);
|
setExplorer(explorer);
|
||||||
} else if (platform === Platform.Emulator) {
|
} else if (platform === Platform.Emulator) {
|
||||||
const explorer = configureEmulator(explorerParams);
|
const explorer = configureEmulator();
|
||||||
setExplorer(explorer);
|
setExplorer(explorer);
|
||||||
} else if (platform === Platform.Portal) {
|
} else if (platform === Platform.Portal) {
|
||||||
const explorer = await configurePortal(explorerParams);
|
const explorer = await configurePortal();
|
||||||
setExplorer(explorer);
|
setExplorer(explorer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,21 +67,21 @@ export function useKnockoutExplorer(platform: Platform, explorerParams: Explorer
|
|||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function configureHosted(explorerParams: ExplorerParams): Promise<Explorer> {
|
async function configureHosted(): Promise<Explorer> {
|
||||||
const win = (window as unknown) as HostedExplorerChildFrame;
|
const win = (window as unknown) as HostedExplorerChildFrame;
|
||||||
if (win.hostedConfig.authType === AuthType.EncryptedToken) {
|
if (win.hostedConfig.authType === AuthType.EncryptedToken) {
|
||||||
return configureHostedWithEncryptedToken(win.hostedConfig, explorerParams);
|
return configureHostedWithEncryptedToken(win.hostedConfig);
|
||||||
} else if (win.hostedConfig.authType === AuthType.ResourceToken) {
|
} else if (win.hostedConfig.authType === AuthType.ResourceToken) {
|
||||||
return configureHostedWithResourceToken(win.hostedConfig, explorerParams);
|
return configureHostedWithResourceToken(win.hostedConfig);
|
||||||
} else if (win.hostedConfig.authType === AuthType.ConnectionString) {
|
} else if (win.hostedConfig.authType === AuthType.ConnectionString) {
|
||||||
return configureHostedWithConnectionString(win.hostedConfig, explorerParams);
|
return configureHostedWithConnectionString(win.hostedConfig);
|
||||||
} else if (win.hostedConfig.authType === AuthType.AAD) {
|
} else if (win.hostedConfig.authType === AuthType.AAD) {
|
||||||
return configureHostedWithAAD(win.hostedConfig, explorerParams);
|
return configureHostedWithAAD(win.hostedConfig);
|
||||||
}
|
}
|
||||||
throw new Error(`Unknown hosted config: ${win.hostedConfig}`);
|
throw new Error(`Unknown hosted config: ${win.hostedConfig}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function configureHostedWithAAD(config: AAD, explorerParams: ExplorerParams): Promise<Explorer> {
|
async function configureHostedWithAAD(config: AAD): Promise<Explorer> {
|
||||||
// TODO: Refactor. updateUserContext needs to be called twice because listKeys below depends on userContext.authorizationToken
|
// TODO: Refactor. updateUserContext needs to be called twice because listKeys below depends on userContext.authorizationToken
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
authType: AuthType.AAD,
|
authType: AuthType.AAD,
|
||||||
@ -120,11 +120,11 @@ async function configureHostedWithAAD(config: AAD, explorerParams: ExplorerParam
|
|||||||
databaseAccount: config.databaseAccount,
|
databaseAccount: config.databaseAccount,
|
||||||
masterKey: keys.primaryMasterKey,
|
masterKey: keys.primaryMasterKey,
|
||||||
});
|
});
|
||||||
const explorer = new Explorer(explorerParams);
|
const explorer = new Explorer();
|
||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|
||||||
function configureHostedWithConnectionString(config: ConnectionString, explorerParams: ExplorerParams): Explorer {
|
function configureHostedWithConnectionString(config: ConnectionString): Explorer {
|
||||||
const apiExperience = DefaultExperienceUtility.getDefaultExperienceFromApiKind(config.encryptedTokenMetadata.apiKind);
|
const apiExperience = DefaultExperienceUtility.getDefaultExperienceFromApiKind(config.encryptedTokenMetadata.apiKind);
|
||||||
const databaseAccount = {
|
const databaseAccount = {
|
||||||
id: "",
|
id: "",
|
||||||
@ -142,11 +142,11 @@ function configureHostedWithConnectionString(config: ConnectionString, explorerP
|
|||||||
databaseAccount,
|
databaseAccount,
|
||||||
masterKey: config.masterKey,
|
masterKey: config.masterKey,
|
||||||
});
|
});
|
||||||
const explorer = new Explorer(explorerParams);
|
const explorer = new Explorer();
|
||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|
||||||
function configureHostedWithResourceToken(config: ResourceToken, explorerParams: ExplorerParams): Explorer {
|
function configureHostedWithResourceToken(config: ResourceToken): Explorer {
|
||||||
const parsedResourceToken = parseResourceTokenConnectionString(config.resourceToken);
|
const parsedResourceToken = parseResourceTokenConnectionString(config.resourceToken);
|
||||||
const databaseAccount = {
|
const databaseAccount = {
|
||||||
id: "",
|
id: "",
|
||||||
@ -167,11 +167,11 @@ function configureHostedWithResourceToken(config: ResourceToken, explorerParams:
|
|||||||
partitionKey: parsedResourceToken.partitionKey,
|
partitionKey: parsedResourceToken.partitionKey,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const explorer = new Explorer(explorerParams);
|
const explorer = new Explorer();
|
||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|
||||||
function configureHostedWithEncryptedToken(config: EncryptedToken, explorerParams: ExplorerParams): Explorer {
|
function configureHostedWithEncryptedToken(config: EncryptedToken): Explorer {
|
||||||
const apiExperience = DefaultExperienceUtility.getDefaultExperienceFromApiKind(config.encryptedTokenMetadata.apiKind);
|
const apiExperience = DefaultExperienceUtility.getDefaultExperienceFromApiKind(config.encryptedTokenMetadata.apiKind);
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
authType: AuthType.EncryptedToken,
|
authType: AuthType.EncryptedToken,
|
||||||
@ -185,20 +185,20 @@ function configureHostedWithEncryptedToken(config: EncryptedToken, explorerParam
|
|||||||
properties: getDatabaseAccountPropertiesFromMetadata(config.encryptedTokenMetadata),
|
properties: getDatabaseAccountPropertiesFromMetadata(config.encryptedTokenMetadata),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const explorer = new Explorer(explorerParams);
|
const explorer = new Explorer();
|
||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|
||||||
function configureEmulator(explorerParams: ExplorerParams): Explorer {
|
function configureEmulator(): Explorer {
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
databaseAccount: emulatorAccount,
|
databaseAccount: emulatorAccount,
|
||||||
authType: AuthType.MasterKey,
|
authType: AuthType.MasterKey,
|
||||||
});
|
});
|
||||||
const explorer = new Explorer(explorerParams);
|
const explorer = new Explorer();
|
||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function configurePortal(explorerParams: ExplorerParams): Promise<Explorer> {
|
async function configurePortal(): Promise<Explorer> {
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
authType: AuthType.AAD,
|
authType: AuthType.AAD,
|
||||||
});
|
});
|
||||||
@ -214,7 +214,7 @@ async function configurePortal(explorerParams: ExplorerParams): Promise<Explorer
|
|||||||
);
|
);
|
||||||
console.dir(message);
|
console.dir(message);
|
||||||
updateContextsFromPortalMessage(message);
|
updateContextsFromPortalMessage(message);
|
||||||
const explorer = new Explorer(explorerParams);
|
const explorer = new Explorer();
|
||||||
// In development mode, save the iframe message from the portal in session storage.
|
// In development mode, save the iframe message from the portal in session storage.
|
||||||
// This allows webpack hot reload to funciton properly
|
// This allows webpack hot reload to funciton properly
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === "development") {
|
||||||
@ -250,7 +250,7 @@ async function configurePortal(explorerParams: ExplorerParams): Promise<Explorer
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateContextsFromPortalMessage(inputs);
|
updateContextsFromPortalMessage(inputs);
|
||||||
const explorer = new Explorer(explorerParams);
|
const explorer = new Explorer();
|
||||||
resolve(explorer);
|
resolve(explorer);
|
||||||
if (openAction) {
|
if (openAction) {
|
||||||
handleOpenAction(openAction, useDatabases.getState().databases, explorer);
|
handleOpenAction(openAction, useDatabases.getState().databases, explorer);
|
||||||
|
@ -1,18 +1,77 @@
|
|||||||
import { useState } from "react";
|
import create, { UseStore } from "zustand";
|
||||||
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import TabsBase from "../Explorer/Tabs/TabsBase";
|
import TabsBase from "../Explorer/Tabs/TabsBase";
|
||||||
import { TabsManager } from "../Explorer/Tabs/TabsManager";
|
|
||||||
import { useObservable } from "./useObservable";
|
|
||||||
|
|
||||||
export type UseTabs = {
|
interface TabsState {
|
||||||
tabs: readonly TabsBase[];
|
openedTabs: TabsBase[];
|
||||||
activeTab: TabsBase;
|
activeTab: TabsBase;
|
||||||
tabsManager: TabsManager;
|
activateTab: (tab: TabsBase) => void;
|
||||||
};
|
activateNewTab: (tab: TabsBase) => void;
|
||||||
|
updateTab: (tab: TabsBase) => void;
|
||||||
export function useTabs(): UseTabs {
|
getTabs: (tabKind: ViewModels.CollectionTabKind, comparator?: (tab: TabsBase) => boolean) => TabsBase[];
|
||||||
const [tabsManager] = useState(() => new TabsManager());
|
refreshActiveTab: (comparator: (tab: TabsBase) => boolean) => void;
|
||||||
const tabs = useObservable(tabsManager.openedTabs);
|
closeTabsByComparator: (comparator: (tab: TabsBase) => boolean) => void;
|
||||||
const activeTab = useObservable(tabsManager.activeTab);
|
closeTab: (tab: TabsBase) => void;
|
||||||
|
|
||||||
return { tabs, activeTab, tabsManager };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useTabs: UseStore<TabsState> = create((set, get) => ({
|
||||||
|
openedTabs: [],
|
||||||
|
activeTab: undefined,
|
||||||
|
activateTab: (tab: TabsBase): void => {
|
||||||
|
if (get().openedTabs.some((openedTab) => openedTab.tabId === tab.tabId)) {
|
||||||
|
set({ activeTab: tab });
|
||||||
|
tab.onActivate();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
activateNewTab: (tab: TabsBase): void => {
|
||||||
|
set((state) => ({ openedTabs: [...state.openedTabs, tab], activeTab: tab }));
|
||||||
|
tab.onActivate();
|
||||||
|
},
|
||||||
|
updateTab: (tab: TabsBase) => {
|
||||||
|
if (get().activeTab?.tabId === tab.tabId) {
|
||||||
|
set({ activeTab: tab });
|
||||||
|
}
|
||||||
|
|
||||||
|
set((state) => ({
|
||||||
|
openedTabs: state.openedTabs.map((openedTab) => {
|
||||||
|
if (openedTab.tabId === tab.tabId) {
|
||||||
|
return tab;
|
||||||
|
}
|
||||||
|
return openedTab;
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
getTabs: (tabKind: ViewModels.CollectionTabKind, comparator?: (tab: TabsBase) => boolean): TabsBase[] =>
|
||||||
|
get().openedTabs.filter((tab) => tab.tabKind === tabKind && (!comparator || comparator(tab))),
|
||||||
|
refreshActiveTab: (comparator: (tab: TabsBase) => boolean): void => {
|
||||||
|
// ensures that the tab selects/highlights the right node based on resource tree expand/collapse state
|
||||||
|
const activeTab = get().activeTab;
|
||||||
|
activeTab && comparator(activeTab) && activeTab.onActivate();
|
||||||
|
},
|
||||||
|
closeTabsByComparator: (comparator: (tab: TabsBase) => boolean): void =>
|
||||||
|
get()
|
||||||
|
.openedTabs.filter(comparator)
|
||||||
|
.forEach((tab) => tab.onCloseTabButtonClick()),
|
||||||
|
closeTab: (tab: TabsBase): void => {
|
||||||
|
let tabIndex: number;
|
||||||
|
const { activeTab, openedTabs } = get();
|
||||||
|
const updatedTabs = openedTabs.filter((openedTab, index) => {
|
||||||
|
if (tab.tabId === openedTab.tabId) {
|
||||||
|
tabIndex = index;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (updatedTabs.length === 0) {
|
||||||
|
set({ activeTab: undefined });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tab.tabId === activeTab.tabId && tabIndex !== -1) {
|
||||||
|
const tabToTheRight = updatedTabs[tabIndex];
|
||||||
|
const lastOpenTab = updatedTabs[updatedTabs.length - 1];
|
||||||
|
set({ activeTab: tabToTheRight || lastOpenTab });
|
||||||
|
}
|
||||||
|
|
||||||
|
set({ openedTabs: updatedTabs });
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
Loading…
Reference in New Issue
Block a user