mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-21 18:01:39 +00:00
Merge branch 'master'
This commit is contained in:
@@ -31,10 +31,6 @@ describe("Collection", () => {
|
||||
function generateMockCollectionWithDataModel(data: DataModels.Collection): Collection {
|
||||
const mockContainer = {} as Explorer;
|
||||
|
||||
mockContainer.isDatabaseNodeOrNoneSelected = () => {
|
||||
return false;
|
||||
};
|
||||
|
||||
return generateCollection(mockContainer, "abc", data, {} as DataModels.Offer);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { UploadDetailsRecord } from "../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../UserContext";
|
||||
@@ -28,11 +29,13 @@ import ConflictsTab from "../Tabs/ConflictsTab";
|
||||
import DocumentsTab from "../Tabs/DocumentsTab";
|
||||
import GraphTab from "../Tabs/GraphTab";
|
||||
import MongoDocumentsTab from "../Tabs/MongoDocumentsTab";
|
||||
import MongoQueryTab from "../Tabs/MongoQueryTab";
|
||||
import MongoShellTab from "../Tabs/MongoShellTab";
|
||||
import QueryTab from "../Tabs/QueryTab";
|
||||
import { NewMongoQueryTab } from "../Tabs/MongoQueryTab/MongoQueryTab";
|
||||
import { NewMongoShellTab } from "../Tabs/MongoShellTab/MongoShellTab";
|
||||
import { NewQueryTab } from "../Tabs/QueryTab/QueryTab";
|
||||
import QueryTablesTab from "../Tabs/QueryTablesTab";
|
||||
import { CollectionSettingsTabV2 } from "../Tabs/SettingsTabV2";
|
||||
import { useDatabases } from "../useDatabases";
|
||||
import { useSelectedNode } from "../useSelectedNode";
|
||||
import ConflictId from "./ConflictId";
|
||||
import DocumentId from "./DocumentId";
|
||||
import StoredProcedure from "./StoredProcedure";
|
||||
@@ -174,6 +177,19 @@ export default class Collection implements ViewModels.Collection {
|
||||
});
|
||||
|
||||
this.children = ko.observableArray<ViewModels.TreeNode>([]);
|
||||
this.children.subscribe(() => {
|
||||
// update the database in zustand store
|
||||
const database = this.getDatabase();
|
||||
database.collections(
|
||||
database.collections()?.map((collection) => {
|
||||
if (collection.id() === this.id()) {
|
||||
return this;
|
||||
}
|
||||
return collection;
|
||||
})
|
||||
);
|
||||
useDatabases.getState().updateDatabase(database);
|
||||
});
|
||||
|
||||
this.storedProcedures = ko.computed(() => {
|
||||
return this.children()
|
||||
@@ -209,7 +225,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
}
|
||||
|
||||
public expandCollapseCollection() {
|
||||
this.container.selectedNode(this);
|
||||
useSelectedNode.getState().setSelectedNode(this);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Collection node",
|
||||
|
||||
@@ -224,9 +240,11 @@ export default class Collection implements ViewModels.Collection {
|
||||
this.expandCollection();
|
||||
}
|
||||
useCommandBar.getState().setContextButtons([]);
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
);
|
||||
useTabs
|
||||
.getState()
|
||||
.refreshActiveTab(
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
);
|
||||
}
|
||||
|
||||
public collapseCollection() {
|
||||
@@ -262,7 +280,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
}
|
||||
|
||||
public onDocumentDBDocumentsClick() {
|
||||
this.container.selectedNode(this);
|
||||
useSelectedNode.getState().setSelectedNode(this);
|
||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Documents node",
|
||||
@@ -273,14 +291,16 @@ export default class Collection implements ViewModels.Collection {
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
|
||||
const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.Documents,
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as DocumentsTab[];
|
||||
const documentsTabs: DocumentsTab[] = useTabs
|
||||
.getState()
|
||||
.getTabs(
|
||||
ViewModels.CollectionTabKind.Documents,
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as DocumentsTab[];
|
||||
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
|
||||
|
||||
if (documentsTab) {
|
||||
this.container.tabsManager.activateTab(documentsTab);
|
||||
useTabs.getState().activateTab(documentsTab);
|
||||
} else {
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||
databaseName: this.databaseId,
|
||||
@@ -299,16 +319,15 @@ export default class Collection implements ViewModels.Collection {
|
||||
collection: this,
|
||||
node: this,
|
||||
tabPath: `${this.databaseId}>${this.id()}>Documents`,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/documents`,
|
||||
onLoadStartKey: startKey,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(documentsTab);
|
||||
useTabs.getState().activateNewTab(documentsTab);
|
||||
}
|
||||
}
|
||||
|
||||
public onConflictsClick() {
|
||||
this.container.selectedNode(this);
|
||||
useSelectedNode.getState().setSelectedNode(this);
|
||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Conflicts);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Conflicts node",
|
||||
@@ -319,14 +338,16 @@ export default class Collection implements ViewModels.Collection {
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
|
||||
const conflictsTabs: ConflictsTab[] = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.Conflicts,
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as ConflictsTab[];
|
||||
const conflictsTabs: ConflictsTab[] = useTabs
|
||||
.getState()
|
||||
.getTabs(
|
||||
ViewModels.CollectionTabKind.Conflicts,
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as ConflictsTab[];
|
||||
let conflictsTab: ConflictsTab = conflictsTabs && conflictsTabs[0];
|
||||
|
||||
if (conflictsTab) {
|
||||
this.container.tabsManager.activateTab(conflictsTab);
|
||||
useTabs.getState().activateTab(conflictsTab);
|
||||
} else {
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||
databaseName: this.databaseId,
|
||||
@@ -345,16 +366,15 @@ export default class Collection implements ViewModels.Collection {
|
||||
collection: this,
|
||||
node: this,
|
||||
tabPath: `${this.databaseId}>${this.id()}>Conflicts`,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/conflicts`,
|
||||
onLoadStartKey: startKey,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(conflictsTab);
|
||||
useTabs.getState().activateNewTab(conflictsTab);
|
||||
}
|
||||
}
|
||||
|
||||
public onTableEntitiesClick() {
|
||||
this.container.selectedNode(this);
|
||||
useSelectedNode.getState().setSelectedNode(this);
|
||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.QueryTables);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Entities node",
|
||||
@@ -371,14 +391,16 @@ export default class Collection implements ViewModels.Collection {
|
||||
});
|
||||
}
|
||||
|
||||
const queryTablesTabs: QueryTablesTab[] = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.QueryTables,
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as QueryTablesTab[];
|
||||
const queryTablesTabs: QueryTablesTab[] = useTabs
|
||||
.getState()
|
||||
.getTabs(
|
||||
ViewModels.CollectionTabKind.QueryTables,
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as QueryTablesTab[];
|
||||
let queryTablesTab: QueryTablesTab = queryTablesTabs && queryTablesTabs[0];
|
||||
|
||||
if (queryTablesTab) {
|
||||
this.container.tabsManager.activateTab(queryTablesTab);
|
||||
useTabs.getState().activateTab(queryTablesTab);
|
||||
} else {
|
||||
this.documentIds([]);
|
||||
let title = `Entities`;
|
||||
@@ -399,16 +421,15 @@ export default class Collection implements ViewModels.Collection {
|
||||
tabPath: "",
|
||||
collection: this,
|
||||
node: this,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/entities`,
|
||||
onLoadStartKey: startKey,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(queryTablesTab);
|
||||
useTabs.getState().activateNewTab(queryTablesTab);
|
||||
}
|
||||
}
|
||||
|
||||
public onGraphDocumentsClick() {
|
||||
this.container.selectedNode(this);
|
||||
useSelectedNode.getState().setSelectedNode(this);
|
||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Graph);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Documents node",
|
||||
@@ -419,14 +440,16 @@ export default class Collection implements ViewModels.Collection {
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
|
||||
const graphTabs: GraphTab[] = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.Graph,
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as GraphTab[];
|
||||
const graphTabs: GraphTab[] = useTabs
|
||||
.getState()
|
||||
.getTabs(
|
||||
ViewModels.CollectionTabKind.Graph,
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as GraphTab[];
|
||||
let graphTab: GraphTab = graphTabs && graphTabs[0];
|
||||
|
||||
if (graphTab) {
|
||||
this.container.tabsManager.activateTab(graphTab);
|
||||
useTabs.getState().activateTab(graphTab);
|
||||
} else {
|
||||
this.documentIds([]);
|
||||
const title = "Graph";
|
||||
@@ -448,19 +471,18 @@ export default class Collection implements ViewModels.Collection {
|
||||
collection: this,
|
||||
masterKey: userContext.masterKey || "",
|
||||
collectionPartitionKeyProperty: this.partitionKeyProperty,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/graphs`,
|
||||
collectionId: this.id(),
|
||||
databaseId: this.databaseId,
|
||||
isTabsContentExpanded: this.container.isTabsContentExpanded,
|
||||
onLoadStartKey: startKey,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(graphTab);
|
||||
useTabs.getState().activateNewTab(graphTab);
|
||||
}
|
||||
}
|
||||
|
||||
public onMongoDBDocumentsClick = () => {
|
||||
this.container.selectedNode(this);
|
||||
useSelectedNode.getState().setSelectedNode(this);
|
||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Documents node",
|
||||
@@ -471,14 +493,16 @@ export default class Collection implements ViewModels.Collection {
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
|
||||
const mongoDocumentsTabs: MongoDocumentsTab[] = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.Documents,
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as MongoDocumentsTab[];
|
||||
const mongoDocumentsTabs: MongoDocumentsTab[] = useTabs
|
||||
.getState()
|
||||
.getTabs(
|
||||
ViewModels.CollectionTabKind.Documents,
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as MongoDocumentsTab[];
|
||||
let mongoDocumentsTab: MongoDocumentsTab = mongoDocumentsTabs && mongoDocumentsTabs[0];
|
||||
|
||||
if (mongoDocumentsTab) {
|
||||
this.container.tabsManager.activateTab(mongoDocumentsTab);
|
||||
useTabs.getState().activateTab(mongoDocumentsTab);
|
||||
} else {
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||
databaseName: this.databaseId,
|
||||
@@ -497,15 +521,14 @@ export default class Collection implements ViewModels.Collection {
|
||||
tabPath: "",
|
||||
collection: this,
|
||||
node: this,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoDocuments`,
|
||||
onLoadStartKey: startKey,
|
||||
});
|
||||
this.container.tabsManager.activateNewTab(mongoDocumentsTab);
|
||||
useTabs.getState().activateNewTab(mongoDocumentsTab);
|
||||
}
|
||||
};
|
||||
|
||||
public onSchemaAnalyzerClick = async () => {
|
||||
this.container.selectedNode(this);
|
||||
useSelectedNode.getState().setSelectedNode(this);
|
||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.SchemaAnalyzer);
|
||||
const SchemaAnalyzerTab = await (await import("../Tabs/SchemaAnalyzerTab")).default;
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
@@ -515,13 +538,13 @@ export default class Collection implements ViewModels.Collection {
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
|
||||
for (const tab of this.container.tabsManager.openedTabs()) {
|
||||
for (const tab of useTabs.getState().openedTabs) {
|
||||
if (
|
||||
tab instanceof SchemaAnalyzerTab &&
|
||||
tab.collection?.databaseId === this.databaseId &&
|
||||
tab.collection?.id() === this.id()
|
||||
) {
|
||||
return this.container.tabsManager.activateTab(tab);
|
||||
return useTabs.getState().activateTab(tab);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,7 +555,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
tabTitle: "Schema",
|
||||
});
|
||||
this.documentIds([]);
|
||||
this.container.tabsManager.activateNewTab(
|
||||
useTabs.getState().activateNewTab(
|
||||
new SchemaAnalyzerTab({
|
||||
account: userContext.databaseAccount,
|
||||
masterKey: userContext.masterKey || "",
|
||||
@@ -542,14 +565,14 @@ export default class Collection implements ViewModels.Collection {
|
||||
tabPath: "",
|
||||
collection: this,
|
||||
node: this,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/schemaAnalyzer`,
|
||||
onLoadStartKey: startKey,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
public onSettingsClick = async (): Promise<void> => {
|
||||
this.container.selectedNode(this);
|
||||
await this.loadOffer();
|
||||
useSelectedNode.getState().setSelectedNode(this);
|
||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Settings);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Settings node",
|
||||
@@ -561,12 +584,9 @@ export default class Collection implements ViewModels.Collection {
|
||||
});
|
||||
|
||||
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
||||
const matchingTabs = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.CollectionSettingsV2,
|
||||
(tab) => {
|
||||
return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
|
||||
}
|
||||
);
|
||||
const matchingTabs = useTabs.getState().getTabs(ViewModels.CollectionTabKind.CollectionSettingsV2, (tab) => {
|
||||
return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
|
||||
});
|
||||
|
||||
const traceStartData = {
|
||||
databaseName: this.databaseId,
|
||||
@@ -582,7 +602,6 @@ export default class Collection implements ViewModels.Collection {
|
||||
tabPath: "",
|
||||
collection: this,
|
||||
node: this,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/settings`,
|
||||
};
|
||||
|
||||
let settingsTabV2 = matchingTabs && (matchingTabs[0] as CollectionSettingsTabV2);
|
||||
@@ -599,15 +618,15 @@ export default class Collection implements ViewModels.Collection {
|
||||
settingsTabOptions.onLoadStartKey = startKey;
|
||||
settingsTabOptions.tabKind = ViewModels.CollectionTabKind.CollectionSettingsV2;
|
||||
settingsTabV2 = new CollectionSettingsTabV2(settingsTabOptions);
|
||||
this.container.tabsManager.activateNewTab(settingsTabV2);
|
||||
useTabs.getState().activateNewTab(settingsTabV2);
|
||||
} else {
|
||||
this.container.tabsManager.activateTab(settingsTabV2);
|
||||
useTabs.getState().activateTab(settingsTabV2);
|
||||
}
|
||||
};
|
||||
|
||||
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
||||
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 startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||
databaseName: this.databaseId,
|
||||
@@ -617,24 +636,26 @@ export default class Collection implements ViewModels.Collection {
|
||||
tabTitle: title,
|
||||
});
|
||||
|
||||
const queryTab: QueryTab = new QueryTab({
|
||||
tabKind: ViewModels.CollectionTabKind.Query,
|
||||
title: title,
|
||||
tabPath: "",
|
||||
collection: this,
|
||||
node: this,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/query`,
|
||||
queryText: queryText,
|
||||
partitionKey: collection.partitionKey,
|
||||
onLoadStartKey: startKey,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(queryTab);
|
||||
useTabs.getState().activateNewTab(
|
||||
new NewQueryTab(
|
||||
{
|
||||
tabKind: ViewModels.CollectionTabKind.Query,
|
||||
title: title,
|
||||
tabPath: "",
|
||||
collection: this,
|
||||
node: this,
|
||||
queryText: queryText,
|
||||
partitionKey: collection.partitionKey,
|
||||
onLoadStartKey: startKey,
|
||||
},
|
||||
{ container: this.container }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public onNewMongoQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
||||
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 startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||
@@ -645,22 +666,27 @@ export default class Collection implements ViewModels.Collection {
|
||||
tabTitle: title,
|
||||
});
|
||||
|
||||
const mongoQueryTab: MongoQueryTab = new MongoQueryTab({
|
||||
tabKind: ViewModels.CollectionTabKind.Query,
|
||||
title: title,
|
||||
tabPath: "",
|
||||
collection: this,
|
||||
node: this,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoQuery`,
|
||||
partitionKey: collection.partitionKey,
|
||||
onLoadStartKey: startKey,
|
||||
});
|
||||
const newMongoQueryTab: NewMongoQueryTab = new NewMongoQueryTab(
|
||||
{
|
||||
tabKind: ViewModels.CollectionTabKind.Query,
|
||||
title: title,
|
||||
tabPath: "",
|
||||
collection: this,
|
||||
node: this,
|
||||
partitionKey: collection.partitionKey,
|
||||
onLoadStartKey: startKey,
|
||||
},
|
||||
{
|
||||
container: this.container,
|
||||
viewModelcollection: this,
|
||||
}
|
||||
);
|
||||
|
||||
this.container.tabsManager.activateNewTab(mongoQueryTab);
|
||||
useTabs.getState().activateNewTab(newMongoQueryTab);
|
||||
}
|
||||
|
||||
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 startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||
@@ -680,37 +706,38 @@ export default class Collection implements ViewModels.Collection {
|
||||
collection: this,
|
||||
masterKey: userContext.masterKey || "",
|
||||
collectionPartitionKeyProperty: this.partitionKeyProperty,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/graphs`,
|
||||
collectionId: this.id(),
|
||||
databaseId: this.databaseId,
|
||||
isTabsContentExpanded: this.container.isTabsContentExpanded,
|
||||
onLoadStartKey: startKey,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(graphTab);
|
||||
useTabs.getState().activateNewTab(graphTab);
|
||||
}
|
||||
|
||||
public onNewMongoShellClick() {
|
||||
const mongoShellTabs = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.MongoShell
|
||||
) as MongoShellTab[];
|
||||
const mongoShellTabs = useTabs.getState().getTabs(ViewModels.CollectionTabKind.MongoShell) as NewMongoShellTab[];
|
||||
|
||||
let index = 1;
|
||||
if (mongoShellTabs.length > 0) {
|
||||
index = mongoShellTabs[mongoShellTabs.length - 1].index + 1;
|
||||
}
|
||||
|
||||
const mongoShellTab: MongoShellTab = new MongoShellTab({
|
||||
tabKind: ViewModels.CollectionTabKind.MongoShell,
|
||||
title: "Shell " + index,
|
||||
tabPath: "",
|
||||
collection: this,
|
||||
node: this,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoShell`,
|
||||
index: index,
|
||||
});
|
||||
const mongoShellTab: NewMongoShellTab = new NewMongoShellTab(
|
||||
{
|
||||
tabKind: ViewModels.CollectionTabKind.MongoShell,
|
||||
title: "Shell " + index,
|
||||
tabPath: "",
|
||||
collection: this,
|
||||
node: this,
|
||||
index: index,
|
||||
},
|
||||
{
|
||||
container: this.container,
|
||||
}
|
||||
);
|
||||
|
||||
this.container.tabsManager.activateNewTab(mongoShellTab);
|
||||
useTabs.getState().activateNewTab(mongoShellTab);
|
||||
}
|
||||
|
||||
public onNewStoredProcedureClick(source: ViewModels.Collection, event: MouseEvent) {
|
||||
@@ -727,21 +754,21 @@ export default class Collection implements ViewModels.Collection {
|
||||
|
||||
public createStoredProcedureNode(data: StoredProcedureDefinition & Resource): StoredProcedure {
|
||||
const node = new StoredProcedure(this.container, this, data);
|
||||
this.container.selectedNode(node);
|
||||
useSelectedNode.getState().setSelectedNode(node);
|
||||
this.children.push(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
public createUserDefinedFunctionNode(data: UserDefinedFunctionDefinition & Resource): UserDefinedFunction {
|
||||
const node = new UserDefinedFunction(this.container, this, data);
|
||||
this.container.selectedNode(node);
|
||||
useSelectedNode.getState().setSelectedNode(node);
|
||||
this.children.push(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
public createTriggerNode(data: TriggerDefinition & Resource): Trigger {
|
||||
const node = new Trigger(this.container, this, data);
|
||||
this.container.selectedNode(node);
|
||||
useSelectedNode.getState().setSelectedNode(node);
|
||||
this.children.push(node);
|
||||
return node;
|
||||
}
|
||||
@@ -768,9 +795,11 @@ export default class Collection implements ViewModels.Collection {
|
||||
} else {
|
||||
this.expandStoredProcedures();
|
||||
}
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
);
|
||||
useTabs
|
||||
.getState()
|
||||
.refreshActiveTab(
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
);
|
||||
}
|
||||
|
||||
public expandStoredProcedures() {
|
||||
@@ -827,9 +856,11 @@ export default class Collection implements ViewModels.Collection {
|
||||
} else {
|
||||
this.expandUserDefinedFunctions();
|
||||
}
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
);
|
||||
useTabs
|
||||
.getState()
|
||||
.refreshActiveTab(
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
);
|
||||
}
|
||||
|
||||
public expandUserDefinedFunctions() {
|
||||
@@ -886,9 +917,11 @@ export default class Collection implements ViewModels.Collection {
|
||||
} else {
|
||||
this.expandTriggers();
|
||||
}
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
);
|
||||
useTabs
|
||||
.getState()
|
||||
.refreshActiveTab(
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
);
|
||||
}
|
||||
|
||||
public expandTriggers() {
|
||||
@@ -1144,7 +1177,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
}
|
||||
|
||||
public getDatabase(): ViewModels.Database {
|
||||
return this.container.findDatabaseWithId(this.databaseId);
|
||||
return useDatabases.getState().findDatabaseWithId(this.databaseId);
|
||||
}
|
||||
|
||||
public async loadOffer(): Promise<void> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as ko from "knockout";
|
||||
import { HttpStatusCodes } from "../../Common/Constants";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { JunoClient } from "../../Juno/JunoClient";
|
||||
import { Features } from "../../Platform/Hosted/extractFeatures";
|
||||
import { updateUserContext, userContext } from "../../UserContext";
|
||||
import Explorer from "../Explorer";
|
||||
import Database from "./Database";
|
||||
@@ -31,11 +31,10 @@ updateUserContext({
|
||||
|
||||
describe("Add Schema", () => {
|
||||
it("should not call requestSchema or getSchema if analyticalStorageTtl is undefined", () => {
|
||||
const collection: DataModels.Collection = {} as DataModels.Collection;
|
||||
const collection: DataModels.Collection = { id: "fakeId" } as DataModels.Collection;
|
||||
collection.analyticalStorageTtl = undefined;
|
||||
const database = new Database(createMockContainer(), { id: "fakeId" });
|
||||
const database = new Database(createMockContainer(), collection);
|
||||
database.container = createMockContainer();
|
||||
database.container.isSchemaEnabled = ko.computed<boolean>(() => false);
|
||||
|
||||
database.junoClient = new JunoClient();
|
||||
database.junoClient.requestSchema = jest.fn();
|
||||
@@ -47,12 +46,16 @@ describe("Add Schema", () => {
|
||||
});
|
||||
|
||||
it("should call requestSchema or getSchema if analyticalStorageTtl is not undefined", () => {
|
||||
const collection: DataModels.Collection = { id: "fakeId" } as DataModels.Collection;
|
||||
const collection: DataModels.Collection = {} as DataModels.Collection;
|
||||
collection.analyticalStorageTtl = 0;
|
||||
|
||||
const database = new Database(createMockContainer(), {});
|
||||
const database = new Database(createMockContainer(), collection);
|
||||
database.container = createMockContainer();
|
||||
database.container.isSchemaEnabled = ko.computed<boolean>(() => true);
|
||||
updateUserContext({
|
||||
features: {
|
||||
enableSchema: true,
|
||||
} as Features,
|
||||
});
|
||||
|
||||
database.junoClient = new JunoClient();
|
||||
database.junoClient.requestSchema = jest.fn();
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as ko from "knockout";
|
||||
import React from "react";
|
||||
import * as _ from "underscore";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
@@ -9,16 +10,21 @@ import * as Logger from "../../Common/Logger";
|
||||
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { useSidePanel } from "../../hooks/useSidePanel";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { IJunoResponse, JunoClient } from "../../Juno/JunoClient";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { getCollectionName } from "../../Utils/APITypeUtils";
|
||||
import { isServerlessAccount } from "../../Utils/CapabilityUtils";
|
||||
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||
import Explorer from "../Explorer";
|
||||
import { AddCollectionPanel } from "../Panes/AddCollectionPanel";
|
||||
import { DatabaseSettingsTabV2 } from "../Tabs/SettingsTabV2";
|
||||
import { useDatabases } from "../useDatabases";
|
||||
import { useSelectedNode } from "../useSelectedNode";
|
||||
import Collection from "./Collection";
|
||||
|
||||
export default class Database implements ViewModels.Database {
|
||||
public nodeKind: string;
|
||||
public container: Explorer;
|
||||
@@ -33,7 +39,7 @@ export default class Database implements ViewModels.Database {
|
||||
public junoClient: JunoClient;
|
||||
private isOfferRead: boolean;
|
||||
|
||||
constructor(container: Explorer, data: any) {
|
||||
constructor(container: Explorer, data: DataModels.Database) {
|
||||
this.nodeKind = "Database";
|
||||
this.container = container;
|
||||
this.self = data._self;
|
||||
@@ -41,6 +47,7 @@ export default class Database implements ViewModels.Database {
|
||||
this.id = ko.observable(data.id);
|
||||
this.offer = ko.observable();
|
||||
this.collections = ko.observableArray<Collection>();
|
||||
this.collections.subscribe(() => useDatabases.getState().updateDatabase(this));
|
||||
this.isDatabaseExpanded = ko.observable<boolean>(false);
|
||||
this.selectedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
|
||||
this.isDatabaseShared = ko.pureComputed(() => {
|
||||
@@ -51,7 +58,7 @@ export default class Database implements ViewModels.Database {
|
||||
}
|
||||
|
||||
public onSettingsClick = () => {
|
||||
this.container.selectedNode(this);
|
||||
useSelectedNode.getState().setSelectedNode(this);
|
||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.DatabaseSettings);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Settings node",
|
||||
@@ -61,7 +68,7 @@ export default class Database implements ViewModels.Database {
|
||||
|
||||
const pendingNotificationsPromise: Promise<DataModels.Notification> = this.getPendingThroughputSplitNotification();
|
||||
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;
|
||||
|
||||
if (!settingsTab) {
|
||||
@@ -71,6 +78,7 @@ export default class Database implements ViewModels.Database {
|
||||
tabTitle: "Scale",
|
||||
});
|
||||
pendingNotificationsPromise.then(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(data: any) => {
|
||||
const pendingNotification: DataModels.Notification = data?.[0];
|
||||
const tabOptions: ViewModels.TabOptions = {
|
||||
@@ -80,14 +88,13 @@ export default class Database implements ViewModels.Database {
|
||||
node: this,
|
||||
rid: this.rid,
|
||||
database: this,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.databasesWithId(this.id())}/settings`,
|
||||
onLoadStartKey: startKey,
|
||||
};
|
||||
settingsTab = new DatabaseSettingsTabV2(tabOptions);
|
||||
settingsTab.pendingNotification(pendingNotification);
|
||||
this.container.tabsManager.activateNewTab(settingsTab);
|
||||
useTabs.getState().activateNewTab(settingsTab);
|
||||
},
|
||||
(error: any) => {
|
||||
(error) => {
|
||||
const errorMessage = getErrorMessage(error);
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.Tab,
|
||||
@@ -110,35 +117,16 @@ export default class Database implements ViewModels.Database {
|
||||
pendingNotificationsPromise.then(
|
||||
(pendingNotification: DataModels.Notification) => {
|
||||
settingsTab.pendingNotification(pendingNotification);
|
||||
this.container.tabsManager.activateTab(settingsTab);
|
||||
useTabs.getState().activateTab(settingsTab);
|
||||
},
|
||||
(error: any) => {
|
||||
() => {
|
||||
settingsTab.pendingNotification(undefined);
|
||||
this.container.tabsManager.activateTab(settingsTab);
|
||||
useTabs.getState().activateTab(settingsTab);
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
public isDatabaseNodeSelected(): boolean {
|
||||
return (
|
||||
!this.isDatabaseExpanded() &&
|
||||
this.container.selectedNode &&
|
||||
this.container.selectedNode() &&
|
||||
this.container.selectedNode().nodeKind === "Database" &&
|
||||
this.container.selectedNode().id() === this.id()
|
||||
);
|
||||
}
|
||||
|
||||
public selectDatabase() {
|
||||
this.container.selectedNode(this);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Database node",
|
||||
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
}
|
||||
|
||||
public async expandDatabase() {
|
||||
if (this.isDatabaseExpanded()) {
|
||||
return;
|
||||
@@ -207,8 +195,14 @@ export default class Database implements ViewModels.Database {
|
||||
this.deleteCollectionsFromList(deltaCollections.toDelete);
|
||||
}
|
||||
|
||||
public openAddCollection(database: Database) {
|
||||
database.container.openAddCollectionPanel(database.id());
|
||||
public async openAddCollection(database: Database): Promise<void> {
|
||||
await useDatabases.getState().loadDatabaseOffers();
|
||||
useSidePanel
|
||||
.getState()
|
||||
.openSidePanel(
|
||||
"New " + getCollectionName(),
|
||||
<AddCollectionPanel explorer={database.container} databaseId={database.id()} />
|
||||
);
|
||||
}
|
||||
|
||||
public findCollectionWithId(collectionId: string): ViewModels.Collection {
|
||||
@@ -238,7 +232,7 @@ export default class Database implements ViewModels.Database {
|
||||
}
|
||||
|
||||
return _.find(notifications, (notification: DataModels.Notification) => {
|
||||
const throughputUpdateRegExp: RegExp = new RegExp("Throughput update (.*) in progress");
|
||||
const throughputUpdateRegExp = new RegExp("Throughput update (.*) in progress");
|
||||
return (
|
||||
notification.kind === "message" &&
|
||||
!notification.collectionName &&
|
||||
@@ -276,7 +270,7 @@ export default class Database implements ViewModels.Database {
|
||||
}
|
||||
);
|
||||
|
||||
let collectionsToDelete: Collection[] = [];
|
||||
const collectionsToDelete: Collection[] = [];
|
||||
ko.utils.arrayForEach(this.collections(), (collection: Collection) => {
|
||||
const collectionPresentInUpdatedList = _.some(
|
||||
updatedCollectionsList,
|
||||
@@ -316,10 +310,10 @@ export default class Database implements ViewModels.Database {
|
||||
}
|
||||
|
||||
public addSchema(collection: DataModels.Collection, interval?: number): NodeJS.Timeout {
|
||||
let checkForSchema: NodeJS.Timeout = null;
|
||||
let checkForSchema: NodeJS.Timeout;
|
||||
interval = interval || 5000;
|
||||
|
||||
if (collection.analyticalStorageTtl !== undefined && this.container.isSchemaEnabled()) {
|
||||
if (collection.analyticalStorageTtl !== undefined && userContext.features.enableSchema) {
|
||||
collection.requestSchema = () => {
|
||||
this.junoClient.requestSchema({
|
||||
id: undefined,
|
||||
@@ -342,7 +336,7 @@ export default class Database implements ViewModels.Database {
|
||||
clearInterval(checkForSchema);
|
||||
}
|
||||
|
||||
if (response.data !== null) {
|
||||
if (response.data !== undefined) {
|
||||
clearInterval(checkForSchema);
|
||||
collection.schema = response.data;
|
||||
}
|
||||
@@ -2,13 +2,16 @@ import * as ko from "knockout";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../UserContext";
|
||||
import Explorer from "../Explorer";
|
||||
import DocumentsTab from "../Tabs/DocumentsTab";
|
||||
import QueryTab from "../Tabs/QueryTab";
|
||||
import { NewQueryTab } from "../Tabs/QueryTab/QueryTab";
|
||||
import TabsBase from "../Tabs/TabsBase";
|
||||
import { useDatabases } from "../useDatabases";
|
||||
import { useSelectedNode } from "../useSelectedNode";
|
||||
import DocumentId from "./DocumentId";
|
||||
|
||||
export default class ResourceTokenCollection implements ViewModels.CollectionBase {
|
||||
@@ -75,7 +78,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
||||
|
||||
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
||||
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 startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||
databaseName: this.databaseId,
|
||||
@@ -85,24 +88,25 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
||||
tabTitle: title,
|
||||
});
|
||||
|
||||
const queryTab: QueryTab = new QueryTab({
|
||||
tabKind: ViewModels.CollectionTabKind.Query,
|
||||
title: title,
|
||||
tabPath: "",
|
||||
collection: this,
|
||||
node: this,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/query`,
|
||||
queryText: queryText,
|
||||
partitionKey: collection.partitionKey,
|
||||
resourceTokenPartitionKey: userContext.parsedResourceToken.partitionKey,
|
||||
onLoadStartKey: startKey,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(queryTab);
|
||||
useTabs.getState().activateNewTab(
|
||||
new NewQueryTab(
|
||||
{
|
||||
tabKind: ViewModels.CollectionTabKind.Query,
|
||||
title: title,
|
||||
tabPath: "",
|
||||
collection: this,
|
||||
node: this,
|
||||
queryText: queryText,
|
||||
partitionKey: collection.partitionKey,
|
||||
onLoadStartKey: startKey,
|
||||
},
|
||||
{ container: this.container }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public onDocumentDBDocumentsClick() {
|
||||
this.container.selectedNode(this);
|
||||
useSelectedNode.getState().setSelectedNode(this);
|
||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Documents node",
|
||||
@@ -112,16 +116,18 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
|
||||
const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.Documents,
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === this.id() &&
|
||||
(tab.collection as ViewModels.CollectionBase).databaseId === this.databaseId
|
||||
) as DocumentsTab[];
|
||||
const documentsTabs: DocumentsTab[] = useTabs
|
||||
.getState()
|
||||
.getTabs(
|
||||
ViewModels.CollectionTabKind.Documents,
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === this.id() &&
|
||||
(tab.collection as ViewModels.CollectionBase).databaseId === this.databaseId
|
||||
) as DocumentsTab[];
|
||||
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
|
||||
|
||||
if (documentsTab) {
|
||||
this.container.tabsManager.activateTab(documentsTab);
|
||||
useTabs.getState().activateTab(documentsTab);
|
||||
} else {
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||
databaseName: this.databaseId,
|
||||
@@ -140,15 +146,14 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
||||
collection: this,
|
||||
node: this,
|
||||
tabPath: `${this.databaseId}>${this.id()}>Documents`,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/documents`,
|
||||
onLoadStartKey: startKey,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(documentsTab);
|
||||
useTabs.getState().activateNewTab(documentsTab);
|
||||
}
|
||||
}
|
||||
|
||||
public getDatabase(): ViewModels.Database {
|
||||
return this.container.findDatabaseWithId(this.databaseId);
|
||||
return useDatabases.getState().findDatabaseWithId(this.databaseId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,114 +1,109 @@
|
||||
import * as ko from "knockout";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import Explorer from "../Explorer";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import TabsBase from "../Tabs/TabsBase";
|
||||
import { ResourceTreeAdapter } from "./ResourceTreeAdapter";
|
||||
import { useSelectedNode } from "../useSelectedNode";
|
||||
|
||||
describe("ResourceTreeAdapter", () => {
|
||||
const mockContainer = (): Explorer =>
|
||||
(({
|
||||
selectedNode: ko.observable<ViewModels.TreeNode>({
|
||||
nodeKind: "nodeKind",
|
||||
rid: "rid",
|
||||
id: ko.observable<string>("id"),
|
||||
}),
|
||||
tabsManager: {
|
||||
activeTab: ko.observable<TabsBase>({
|
||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||
} as TabsBase),
|
||||
},
|
||||
isNotebookEnabled: ko.observable<boolean>(true),
|
||||
databases: ko.observable<ViewModels.Database[]>([]),
|
||||
} as unknown) as Explorer);
|
||||
describe("useSelectedNode", () => {
|
||||
const mockTab = {
|
||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||
} as TabsBase;
|
||||
|
||||
// TODO isDataNodeSelected needs a better design and refactor, but for now, we protect some of the code paths
|
||||
describe("isDataNodeSelected", () => {
|
||||
afterEach(() => {
|
||||
useSelectedNode.getState().setSelectedNode(undefined);
|
||||
useTabs.setState({ activeTab: undefined });
|
||||
});
|
||||
it("it should not select if no selected node", () => {
|
||||
const explorer = mockContainer();
|
||||
explorer.selectedNode(undefined);
|
||||
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
||||
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("foo", "bar", undefined);
|
||||
useTabs.setState({ activeTab: mockTab });
|
||||
const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected("foo", "bar", undefined);
|
||||
expect(isDataNodeSelected).toBeFalsy();
|
||||
});
|
||||
|
||||
it("it should not select incorrect subnodekinds", () => {
|
||||
const resourceTreeAdapter = new ResourceTreeAdapter(mockContainer());
|
||||
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("foo", "bar", undefined);
|
||||
useTabs.setState({ activeTab: mockTab });
|
||||
useSelectedNode.getState().setSelectedNode({
|
||||
nodeKind: "nodeKind",
|
||||
rid: "rid",
|
||||
id: ko.observable<string>("id"),
|
||||
});
|
||||
const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected("foo", "bar", undefined);
|
||||
expect(isDataNodeSelected).toBeFalsy();
|
||||
});
|
||||
|
||||
it("it should not select if no active tab", () => {
|
||||
const explorer = mockContainer();
|
||||
explorer.tabsManager.activeTab(undefined);
|
||||
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
||||
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("foo", "bar", undefined);
|
||||
useSelectedNode.getState().setSelectedNode({
|
||||
nodeKind: "nodeKind",
|
||||
rid: "rid",
|
||||
id: ko.observable<string>("id"),
|
||||
});
|
||||
const isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected("foo", "bar", undefined);
|
||||
expect(isDataNodeSelected).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should select if correct database node regardless of subnodekinds", () => {
|
||||
useTabs.setState({ activeTab: mockTab });
|
||||
const subNodeKind = ViewModels.CollectionTabKind.Documents;
|
||||
const explorer = mockContainer();
|
||||
explorer.selectedNode(({
|
||||
useSelectedNode.getState().setSelectedNode({
|
||||
nodeKind: "Database",
|
||||
rid: "dbrid",
|
||||
id: ko.observable<string>("dbid"),
|
||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind),
|
||||
} as unknown) as ViewModels.TreeNode);
|
||||
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
||||
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbid", undefined, [
|
||||
ViewModels.CollectionTabKind.Documents,
|
||||
]);
|
||||
} as ViewModels.TreeNode);
|
||||
const isDataNodeSelected = useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected("dbid", undefined, [ViewModels.CollectionTabKind.Documents]);
|
||||
expect(isDataNodeSelected).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should select correct collection node (documents or graph node)", () => {
|
||||
let subNodeKind = ViewModels.CollectionTabKind.Documents;
|
||||
const explorer = mockContainer();
|
||||
explorer.tabsManager.activeTab({
|
||||
let activeTab = {
|
||||
tabKind: subNodeKind,
|
||||
} as TabsBase);
|
||||
explorer.selectedNode(({
|
||||
} as TabsBase;
|
||||
useTabs.setState({ activeTab });
|
||||
useSelectedNode.getState().setSelectedNode({
|
||||
nodeKind: "Collection",
|
||||
rid: "collrid",
|
||||
databaseId: "dbid",
|
||||
id: ko.observable<string>("collid"),
|
||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind),
|
||||
} as unknown) as ViewModels.TreeNode);
|
||||
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
||||
let isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbid", "collid", [subNodeKind]);
|
||||
} as ViewModels.TreeNode);
|
||||
let isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected("dbid", "collid", [subNodeKind]);
|
||||
expect(isDataNodeSelected).toBeTruthy();
|
||||
|
||||
subNodeKind = ViewModels.CollectionTabKind.Graph;
|
||||
explorer.tabsManager.activeTab({
|
||||
activeTab = {
|
||||
tabKind: subNodeKind,
|
||||
} as TabsBase);
|
||||
explorer.selectedNode(({
|
||||
} as TabsBase;
|
||||
useTabs.setState({ activeTab });
|
||||
useSelectedNode.getState().setSelectedNode({
|
||||
nodeKind: "Collection",
|
||||
rid: "collrid",
|
||||
databaseId: "dbid",
|
||||
id: ko.observable<string>("collid"),
|
||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind),
|
||||
} as unknown) as ViewModels.TreeNode);
|
||||
isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbid", "collid", [subNodeKind]);
|
||||
} as ViewModels.TreeNode);
|
||||
isDataNodeSelected = useSelectedNode.getState().isDataNodeSelected("dbid", "collid", [subNodeKind]);
|
||||
expect(isDataNodeSelected).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should not select incorrect collection node (e.g. Settings)", () => {
|
||||
const explorer = mockContainer();
|
||||
explorer.selectedNode(({
|
||||
useSelectedNode.getState().setSelectedNode({
|
||||
nodeKind: "Collection",
|
||||
rid: "collrid",
|
||||
databaseId: "dbid",
|
||||
id: ko.observable<string>("collid"),
|
||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(ViewModels.CollectionTabKind.Documents),
|
||||
} as unknown) as ViewModels.TreeNode);
|
||||
explorer.tabsManager.activeTab({
|
||||
} as ViewModels.TreeNode);
|
||||
const activeTab = {
|
||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||
} as TabsBase);
|
||||
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
||||
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbid", "collid", [
|
||||
ViewModels.CollectionTabKind.Settings,
|
||||
]);
|
||||
} as TabsBase;
|
||||
useTabs.setState({ activeTab });
|
||||
const isDataNodeSelected = useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected("dbid", "collid", [ViewModels.CollectionTabKind.Settings]);
|
||||
expect(isDataNodeSelected).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { shallow } from "enzyme";
|
||||
import * as ko from "knockout";
|
||||
import React from "react";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
@@ -208,13 +207,6 @@ const schema: DataModels.ISchema = {
|
||||
],
|
||||
};
|
||||
|
||||
const createMockContainer = (): Explorer => {
|
||||
const mockContainer = new Explorer();
|
||||
mockContainer.selectedNode = ko.observable<ViewModels.TreeNode>();
|
||||
|
||||
return mockContainer;
|
||||
};
|
||||
|
||||
const createMockCollection = (): ViewModels.Collection => {
|
||||
const mockCollection = {} as DataModels.Collection;
|
||||
mockCollection._rid = "fakeRid";
|
||||
@@ -223,17 +215,13 @@ const createMockCollection = (): ViewModels.Collection => {
|
||||
mockCollection.analyticalStorageTtl = 0;
|
||||
mockCollection.schema = schema;
|
||||
|
||||
const mockCollectionVM: ViewModels.Collection = new Collection(
|
||||
createMockContainer(),
|
||||
"fakeDatabaseId",
|
||||
mockCollection
|
||||
);
|
||||
const mockCollectionVM: ViewModels.Collection = new Collection(new Explorer(), "fakeDatabaseId", mockCollection);
|
||||
|
||||
return mockCollectionVM;
|
||||
};
|
||||
|
||||
describe("Resource tree for schema", () => {
|
||||
const mockContainer: Explorer = createMockContainer();
|
||||
const mockContainer = new Explorer();
|
||||
const resourceTree = new ResourceTreeAdapter(mockContainer);
|
||||
|
||||
it("should render", () => {
|
||||
|
||||
@@ -12,11 +12,11 @@ import PublishIcon from "../../../images/notebook/publish_content.svg";
|
||||
import RefreshIcon from "../../../images/refresh-cosmos.svg";
|
||||
import CollectionIcon from "../../../images/tree-collection.svg";
|
||||
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
||||
import { ArrayHashMap } from "../../Common/ArrayHashMap";
|
||||
import { Areas } from "../../Common/Constants";
|
||||
import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { IPinnedRepo } from "../../Juno/JunoClient";
|
||||
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
|
||||
import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
@@ -24,7 +24,7 @@ import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { isServerlessAccount } from "../../Utils/CapabilityUtils";
|
||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
||||
import { ResourceTreeContextMenuButtonFactory } from "../ContextMenuButtonFactory";
|
||||
import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFactory";
|
||||
import { AccordionComponent, AccordionItemComponent } from "../Controls/Accordion/AccordionComponent";
|
||||
import { TreeComponent, TreeNode, TreeNodeMenuItem } from "../Controls/TreeComponent/TreeComponent";
|
||||
import Explorer from "../Explorer";
|
||||
@@ -32,7 +32,10 @@ import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
|
||||
import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity";
|
||||
import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem";
|
||||
import { NotebookUtil } from "../Notebook/NotebookUtil";
|
||||
import { useNotebook } from "../Notebook/useNotebook";
|
||||
import TabsBase from "../Tabs/TabsBase";
|
||||
import { useDatabases } from "../useDatabases";
|
||||
import { useSelectedNode } from "../useSelectedNode";
|
||||
import StoredProcedure from "./StoredProcedure";
|
||||
import Trigger from "./Trigger";
|
||||
import UserDefinedFunction from "./UserDefinedFunction";
|
||||
@@ -51,30 +54,20 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
public myNotebooksContentRoot: NotebookContentItem;
|
||||
public gitHubNotebooksContentRoot: NotebookContentItem;
|
||||
|
||||
private koSubsDatabaseIdMap: ArrayHashMap<ko.Subscription>; // database id -> ko subs
|
||||
private koSubsCollectionIdMap: ArrayHashMap<ko.Subscription>; // collection id -> ko subs
|
||||
private databaseCollectionIdMap: ArrayHashMap<string>; // database id -> collection ids
|
||||
|
||||
public constructor(private container: Explorer) {
|
||||
this.parameters = ko.observable(Date.now());
|
||||
|
||||
this.container.selectedNode.subscribe((newValue: any) => this.triggerRender());
|
||||
this.container.tabsManager.activeTab.subscribe((newValue: TabsBase) => this.triggerRender());
|
||||
this.container.isNotebookEnabled.subscribe((newValue) => this.triggerRender());
|
||||
useSelectedNode.subscribe(() => this.triggerRender());
|
||||
useTabs.subscribe(
|
||||
() => this.triggerRender(),
|
||||
(state) => state.activeTab
|
||||
);
|
||||
useNotebook.subscribe(
|
||||
() => this.triggerRender(),
|
||||
(state) => state.isNotebookEnabled
|
||||
);
|
||||
|
||||
this.koSubsDatabaseIdMap = new ArrayHashMap();
|
||||
this.koSubsCollectionIdMap = new ArrayHashMap();
|
||||
this.databaseCollectionIdMap = new ArrayHashMap();
|
||||
|
||||
this.container.databases.subscribe((databases: ViewModels.Database[]) => {
|
||||
// Clean up old databases
|
||||
this.cleanupDatabasesKoSubs();
|
||||
|
||||
databases.forEach((database: ViewModels.Database) => this.watchDatabase(database));
|
||||
this.triggerRender();
|
||||
});
|
||||
|
||||
this.container.databases().forEach((database: ViewModels.Database) => this.watchDatabase(database));
|
||||
useDatabases.subscribe(() => this.triggerRender());
|
||||
this.triggerRender();
|
||||
}
|
||||
|
||||
@@ -106,7 +99,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
const dataRootNode = this.buildDataTree();
|
||||
const notebooksRootNode = this.buildNotebooksTrees();
|
||||
|
||||
if (this.container.isNotebookEnabled()) {
|
||||
if (useNotebook.getState().isNotebookEnabled) {
|
||||
return (
|
||||
<>
|
||||
<AccordionComponent>
|
||||
@@ -137,12 +130,12 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
|
||||
this.myNotebooksContentRoot = {
|
||||
name: ResourceTreeAdapter.MyNotebooksTitle,
|
||||
path: this.container.getNotebookBasePath(),
|
||||
path: useNotebook.getState().notebookBasePath,
|
||||
type: NotebookContentItemType.Directory,
|
||||
};
|
||||
|
||||
// Only if notebook server is available we can refresh
|
||||
if (this.container.notebookServerInfo().notebookServerEndpoint) {
|
||||
if (useNotebook.getState().notebookServerInfo?.notebookServerEndpoint) {
|
||||
refreshTasks.push(
|
||||
this.container.refreshContentItem(this.myNotebooksContentRoot).then(() => {
|
||||
this.triggerRender();
|
||||
@@ -192,14 +185,14 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
}
|
||||
|
||||
private buildDataTree(): TreeNode {
|
||||
const databaseTreeNodes: TreeNode[] = this.container.databases().map((database: ViewModels.Database) => {
|
||||
const databaseTreeNodes: TreeNode[] = useDatabases.getState().databases.map((database: ViewModels.Database) => {
|
||||
const databaseNode: TreeNode = {
|
||||
label: database.id(),
|
||||
iconSrc: CosmosDBIcon,
|
||||
isExpanded: false,
|
||||
className: "databaseHeader",
|
||||
children: [],
|
||||
isSelected: () => this.isDataNodeSelected(database.id()),
|
||||
isSelected: () => useSelectedNode.getState().isDataNodeSelected(database.id()),
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createDatabaseContextMenu(this.container, database.id()),
|
||||
onClick: async (isExpanded) => {
|
||||
// Rewritten version of expandCollapseDatabase():
|
||||
@@ -212,18 +205,20 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
await database.expandDatabase();
|
||||
}
|
||||
databaseNode.isLoading = false;
|
||||
database.selectDatabase();
|
||||
useSelectedNode.getState().setSelectedNode(database);
|
||||
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: () => this.container.selectedNode(database),
|
||||
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(database),
|
||||
};
|
||||
|
||||
if (database.isDatabaseShared()) {
|
||||
databaseNode.children.push({
|
||||
label: "Scale",
|
||||
isSelected: () =>
|
||||
this.isDataNodeSelected(database.id(), undefined, [ViewModels.CollectionTabKind.DatabaseSettings]),
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(database.id(), undefined, [ViewModels.CollectionTabKind.DatabaseSettings]),
|
||||
onClick: database.onSettingsClick.bind(database),
|
||||
});
|
||||
}
|
||||
@@ -269,21 +264,27 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
mostRecentActivity.collectionWasOpened(userContext.databaseAccount?.id, collection);
|
||||
},
|
||||
isSelected: () =>
|
||||
this.isDataNodeSelected(collection.databaseId, collection.id(), [
|
||||
ViewModels.CollectionTabKind.Documents,
|
||||
ViewModels.CollectionTabKind.Graph,
|
||||
]),
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(collection.databaseId, collection.id(), [
|
||||
ViewModels.CollectionTabKind.Documents,
|
||||
ViewModels.CollectionTabKind.Graph,
|
||||
]),
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(this.container, collection),
|
||||
});
|
||||
|
||||
if (this.container.isNotebookEnabled() && userContext.apiType === "Mongo" && isPublicInternetAccessAllowed()) {
|
||||
if (
|
||||
useNotebook.getState().isNotebookEnabled &&
|
||||
userContext.apiType === "Mongo" &&
|
||||
isPublicInternetAccessAllowed()
|
||||
) {
|
||||
children.push({
|
||||
label: "Schema (Preview)",
|
||||
onClick: collection.onSchemaAnalyzerClick.bind(collection),
|
||||
isSelected: () =>
|
||||
this.isDataNodeSelected(collection.databaseId, collection.id(), [
|
||||
ViewModels.CollectionTabKind.SchemaAnalyzer,
|
||||
]),
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.SchemaAnalyzer]),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -292,7 +293,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
label: database.isDatabaseShared() || isServerlessAccount() ? "Settings" : "Scale & Settings",
|
||||
onClick: collection.onSettingsClick.bind(collection),
|
||||
isSelected: () =>
|
||||
this.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Settings]),
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Settings]),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -318,7 +321,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
label: "Conflicts",
|
||||
onClick: collection.onConflictsClick.bind(collection),
|
||||
isSelected: () =>
|
||||
this.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Conflicts]),
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Conflicts]),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -331,12 +336,14 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(this.container, collection),
|
||||
onClick: () => {
|
||||
// Rewritten version of expandCollapseCollection
|
||||
this.container.selectedNode(collection);
|
||||
useSelectedNode.getState().setSelectedNode(collection);
|
||||
useCommandBar.getState().setContextButtons([]);
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
useTabs
|
||||
.getState()
|
||||
.refreshActiveTab(
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
},
|
||||
onExpanded: () => {
|
||||
if (ResourceTreeAdapter.showScriptNodes(this.container)) {
|
||||
@@ -345,8 +352,8 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
collection.loadTriggers();
|
||||
}
|
||||
},
|
||||
isSelected: () => this.isDataNodeSelected(collection.databaseId, collection.id()),
|
||||
onContextMenuOpen: () => this.container.selectedNode(collection),
|
||||
isSelected: () => useSelectedNode.getState().isDataNodeSelected(collection.databaseId, collection.id()),
|
||||
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(collection),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -357,17 +364,21 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
label: sp.id(),
|
||||
onClick: sp.open.bind(sp),
|
||||
isSelected: () =>
|
||||
this.isDataNodeSelected(collection.databaseId, collection.id(), [
|
||||
ViewModels.CollectionTabKind.StoredProcedures,
|
||||
]),
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(collection.databaseId, collection.id(), [
|
||||
ViewModels.CollectionTabKind.StoredProcedures,
|
||||
]),
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createStoreProcedureContextMenuItems(this.container, sp),
|
||||
})),
|
||||
onClick: () => {
|
||||
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.StoredProcedures);
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
useTabs
|
||||
.getState()
|
||||
.refreshActiveTab(
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -379,9 +390,11 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
label: udf.id(),
|
||||
onClick: udf.open.bind(udf),
|
||||
isSelected: () =>
|
||||
this.isDataNodeSelected(collection.databaseId, collection.id(), [
|
||||
ViewModels.CollectionTabKind.UserDefinedFunctions,
|
||||
]),
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(collection.databaseId, collection.id(), [
|
||||
ViewModels.CollectionTabKind.UserDefinedFunctions,
|
||||
]),
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createUserDefinedFunctionContextMenuItems(
|
||||
this.container,
|
||||
udf
|
||||
@@ -389,10 +402,12 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
})),
|
||||
onClick: () => {
|
||||
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.UserDefinedFunctions);
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
useTabs
|
||||
.getState()
|
||||
.refreshActiveTab(
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -404,15 +419,19 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
label: trigger.id(),
|
||||
onClick: trigger.open.bind(trigger),
|
||||
isSelected: () =>
|
||||
this.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Triggers]),
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Triggers]),
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createTriggerContextMenuItems(this.container, trigger),
|
||||
})),
|
||||
onClick: () => {
|
||||
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Triggers);
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
useTabs
|
||||
.getState()
|
||||
.refreshActiveTab(
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -431,9 +450,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
children: this.getSchemaNodes(collection.schema.fields),
|
||||
onClick: () => {
|
||||
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Schema);
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
(tab: TabsBase) => tab.collection && tab.collection.rid === collection.rid
|
||||
);
|
||||
useTabs.getState().refreshActiveTab((tab: TabsBase) => tab.collection && tab.collection.rid === collection.rid);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -563,7 +580,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
className: "notebookHeader galleryHeader",
|
||||
onClick: () => this.container.openGallery(),
|
||||
isSelected: () => {
|
||||
const activeTab = this.container.tabsManager.activeTab();
|
||||
const activeTab = useTabs.getState().activeTab;
|
||||
return activeTab && activeTab.tabKind === ViewModels.CollectionTabKind.Gallery;
|
||||
},
|
||||
};
|
||||
@@ -657,7 +674,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
className: "notebookHeader",
|
||||
onClick: () => onFileClick(item),
|
||||
isSelected: () => {
|
||||
const activeTab = this.container.tabsManager.activeTab();
|
||||
const activeTab = useTabs.getState().activeTab;
|
||||
return (
|
||||
activeTab &&
|
||||
activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
|
||||
@@ -812,7 +829,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
}
|
||||
},
|
||||
isSelected: () => {
|
||||
const activeTab = this.container.tabsManager.activeTab();
|
||||
const activeTab = useTabs.getState().activeTab;
|
||||
return (
|
||||
activeTab &&
|
||||
activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
|
||||
@@ -834,133 +851,4 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
public triggerRender() {
|
||||
window.requestAnimationFrame(() => this.parameters(Date.now()));
|
||||
}
|
||||
|
||||
/**
|
||||
* public for testing purposes
|
||||
* @param databaseId
|
||||
* @param collectionId
|
||||
* @param subnodeKinds
|
||||
*/
|
||||
public isDataNodeSelected(
|
||||
databaseId: string,
|
||||
collectionId?: string,
|
||||
subnodeKinds?: ViewModels.CollectionTabKind[]
|
||||
): boolean {
|
||||
if (!this.container.selectedNode || !this.container.selectedNode()) {
|
||||
return false;
|
||||
}
|
||||
const selectedNode = this.container.selectedNode();
|
||||
const isNodeSelected = collectionId
|
||||
? (selectedNode as ViewModels.Collection).databaseId === databaseId && selectedNode.id() === collectionId
|
||||
: selectedNode.id() === databaseId;
|
||||
|
||||
if (!isNodeSelected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (subnodeKinds === undefined || !Array.isArray(subnodeKinds)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const activeTab = this.container.tabsManager.activeTab();
|
||||
const selectedSubnodeKind = collectionId
|
||||
? (selectedNode as ViewModels.Collection).selectedSubnodeKind()
|
||||
: (selectedNode as ViewModels.Database).selectedSubnodeKind();
|
||||
|
||||
return (
|
||||
activeTab &&
|
||||
subnodeKinds.includes(activeTab.tabKind) &&
|
||||
selectedSubnodeKind !== undefined &&
|
||||
subnodeKinds.includes(selectedSubnodeKind)
|
||||
);
|
||||
}
|
||||
|
||||
// *************** watch all nested ko's inside database
|
||||
// TODO Simplify so we don't have to do this
|
||||
|
||||
private watchCollection(databaseId: string, collection: ViewModels.Collection) {
|
||||
this.addKoSubToCollectionId(
|
||||
databaseId,
|
||||
collection.id(),
|
||||
collection.storedProcedures.subscribe(() => {
|
||||
this.triggerRender();
|
||||
})
|
||||
);
|
||||
|
||||
this.addKoSubToCollectionId(
|
||||
databaseId,
|
||||
collection.id(),
|
||||
collection.isCollectionExpanded.subscribe(() => {
|
||||
this.triggerRender();
|
||||
})
|
||||
);
|
||||
|
||||
this.addKoSubToCollectionId(
|
||||
databaseId,
|
||||
collection.id(),
|
||||
collection.isStoredProceduresExpanded.subscribe(() => {
|
||||
this.triggerRender();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private watchDatabase(database: ViewModels.Database) {
|
||||
const databaseId = database.id();
|
||||
const koSub = database.collections.subscribe((collections: ViewModels.Collection[]) => {
|
||||
this.cleanupCollectionsKoSubs(
|
||||
databaseId,
|
||||
collections.map((collection: ViewModels.Collection) => collection.id())
|
||||
);
|
||||
|
||||
collections.forEach((collection: ViewModels.Collection) => this.watchCollection(databaseId, collection));
|
||||
this.triggerRender();
|
||||
});
|
||||
this.addKoSubToDatabaseId(databaseId, koSub);
|
||||
|
||||
database.collections().forEach((collection: ViewModels.Collection) => this.watchCollection(databaseId, collection));
|
||||
}
|
||||
|
||||
private addKoSubToDatabaseId(databaseId: string, sub: ko.Subscription): void {
|
||||
this.koSubsDatabaseIdMap.push(databaseId, sub);
|
||||
}
|
||||
|
||||
private addKoSubToCollectionId(databaseId: string, collectionId: string, sub: ko.Subscription): void {
|
||||
this.databaseCollectionIdMap.push(databaseId, collectionId);
|
||||
this.koSubsCollectionIdMap.push(collectionId, sub);
|
||||
}
|
||||
|
||||
private cleanupDatabasesKoSubs(): void {
|
||||
for (const databaseId of this.koSubsDatabaseIdMap.keys()) {
|
||||
this.koSubsDatabaseIdMap.get(databaseId).forEach((sub: ko.Subscription) => sub.dispose());
|
||||
this.koSubsDatabaseIdMap.delete(databaseId);
|
||||
|
||||
if (this.databaseCollectionIdMap.has(databaseId)) {
|
||||
this.databaseCollectionIdMap
|
||||
.get(databaseId)
|
||||
.forEach((collectionId: string) => this.cleanupKoSubsForCollection(databaseId, collectionId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private cleanupCollectionsKoSubs(databaseId: string, existingCollectionIds: string[]): void {
|
||||
if (!this.databaseCollectionIdMap.has(databaseId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const collectionIdsToRemove = this.databaseCollectionIdMap
|
||||
.get(databaseId)
|
||||
.filter((id: string) => existingCollectionIds.indexOf(id) === -1);
|
||||
|
||||
collectionIdsToRemove.forEach((id: string) => this.cleanupKoSubsForCollection(databaseId, id));
|
||||
}
|
||||
|
||||
private cleanupKoSubsForCollection(databaseId: string, collectionId: string) {
|
||||
if (!this.koSubsCollectionIdMap.has(collectionId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.koSubsCollectionIdMap.get(collectionId).forEach((sub: ko.Subscription) => sub.dispose());
|
||||
this.koSubsCollectionIdMap.delete(collectionId);
|
||||
this.databaseCollectionIdMap.remove(databaseId, collectionId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,27 @@
|
||||
import { shallow } from "enzyme";
|
||||
import * as ko from "knockout";
|
||||
import React from "react";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { TreeComponent, TreeComponentProps, TreeNode } from "../Controls/TreeComponent/TreeComponent";
|
||||
import Explorer from "../Explorer";
|
||||
import { useDatabases } from "../useDatabases";
|
||||
import ResourceTokenCollection from "./ResourceTokenCollection";
|
||||
import { ResourceTreeAdapterForResourceToken } from "./ResourceTreeAdapterForResourceToken";
|
||||
|
||||
const createMockContainer = (): Explorer => {
|
||||
let mockContainer = {} as Explorer;
|
||||
mockContainer.resourceTokenCollection = createMockCollection(mockContainer);
|
||||
mockContainer.selectedNode = ko.observable<ViewModels.TreeNode>();
|
||||
|
||||
return mockContainer;
|
||||
};
|
||||
|
||||
const createMockCollection = (container: Explorer): ko.Observable<ViewModels.CollectionBase> => {
|
||||
let mockCollection = {} as DataModels.Collection;
|
||||
mockCollection._rid = "fakeRid";
|
||||
mockCollection._self = "fakeSelf";
|
||||
mockCollection.id = "fakeId";
|
||||
|
||||
describe("Resource tree for resource token", () => {
|
||||
const mockContainer = {} as Explorer;
|
||||
const resourceTree = new ResourceTreeAdapterForResourceToken(mockContainer);
|
||||
const mockCollection = {
|
||||
_rid: "fakeRid",
|
||||
_self: "fakeSelf",
|
||||
id: "fakeId",
|
||||
} as DataModels.Collection;
|
||||
const mockResourceTokenCollection: ViewModels.CollectionBase = new ResourceTokenCollection(
|
||||
container,
|
||||
mockContainer,
|
||||
"fakeDatabaseId",
|
||||
mockCollection
|
||||
);
|
||||
return ko.observable<ViewModels.CollectionBase>(mockResourceTokenCollection);
|
||||
};
|
||||
|
||||
describe("Resource tree for resource token", () => {
|
||||
const mockContainer: Explorer = createMockContainer();
|
||||
const resourceTree = new ResourceTreeAdapterForResourceToken(mockContainer);
|
||||
useDatabases.setState({ resourceTokenCollection: mockResourceTokenCollection });
|
||||
|
||||
it("should render", () => {
|
||||
const rootNode: TreeNode = resourceTree.buildCollectionNode();
|
||||
|
||||
@@ -3,12 +3,15 @@ import * as React from "react";
|
||||
import CollectionIcon from "../../../images/tree-collection.svg";
|
||||
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { TreeComponent, TreeNode } from "../Controls/TreeComponent/TreeComponent";
|
||||
import Explorer from "../Explorer";
|
||||
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
|
||||
import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity";
|
||||
import { NotebookContentItem } from "../Notebook/NotebookContentItem";
|
||||
import { useDatabases } from "../useDatabases";
|
||||
import { useSelectedNode } from "../useSelectedNode";
|
||||
|
||||
export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
|
||||
public parameters: ko.Observable<number>;
|
||||
@@ -17,9 +20,15 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
|
||||
public constructor(private container: Explorer) {
|
||||
this.parameters = ko.observable(Date.now());
|
||||
|
||||
this.container.resourceTokenCollection.subscribe(() => this.triggerRender());
|
||||
this.container.selectedNode.subscribe((newValue: any) => this.triggerRender());
|
||||
this.container.tabsManager && this.container.tabsManager.activeTab.subscribe(() => this.triggerRender());
|
||||
useDatabases.subscribe(
|
||||
() => this.triggerRender(),
|
||||
(state) => state.resourceTokenCollection
|
||||
);
|
||||
useSelectedNode.subscribe(() => this.triggerRender());
|
||||
useTabs.subscribe(
|
||||
() => this.triggerRender(),
|
||||
(state) => state.activeTab
|
||||
);
|
||||
|
||||
this.triggerRender();
|
||||
}
|
||||
@@ -30,7 +39,7 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
|
||||
}
|
||||
|
||||
public buildCollectionNode(): TreeNode {
|
||||
const collection: ViewModels.CollectionBase = this.container.resourceTokenCollection();
|
||||
const collection: ViewModels.CollectionBase = useDatabases.getState().resourceTokenCollection;
|
||||
if (!collection) {
|
||||
return {
|
||||
label: undefined,
|
||||
@@ -48,7 +57,9 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
|
||||
mostRecentActivity.collectionWasOpened(userContext.databaseAccount?.id, collection);
|
||||
},
|
||||
isSelected: () =>
|
||||
this.isDataNodeSelected(collection.databaseId, collection.id(), ViewModels.CollectionTabKind.Documents),
|
||||
useSelectedNode
|
||||
.getState()
|
||||
.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Documents]),
|
||||
});
|
||||
|
||||
const collectionNode: TreeNode = {
|
||||
@@ -59,13 +70,15 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
|
||||
className: "collectionHeader",
|
||||
onClick: () => {
|
||||
// Rewritten version of expandCollapseCollection
|
||||
this.container.selectedNode(collection);
|
||||
useSelectedNode.getState().setSelectedNode(collection);
|
||||
useCommandBar.getState().setContextButtons([]);
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
(tab) => tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
useTabs
|
||||
.getState()
|
||||
.refreshActiveTab(
|
||||
(tab) => tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
},
|
||||
isSelected: () => this.isDataNodeSelected(collection.databaseId, collection.id()),
|
||||
isSelected: () => useSelectedNode.getState().isDataNodeSelected(collection.databaseId, collection.id()),
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -75,35 +88,6 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
|
||||
};
|
||||
}
|
||||
|
||||
public isDataNodeSelected(
|
||||
databaseId: string,
|
||||
collectionId?: string,
|
||||
subnodeKind?: ViewModels.CollectionTabKind
|
||||
): boolean {
|
||||
if (!this.container.selectedNode || !this.container.selectedNode()) {
|
||||
return false;
|
||||
}
|
||||
const selectedNode = this.container.selectedNode();
|
||||
const isNodeSelected = collectionId
|
||||
? (selectedNode as ViewModels.Collection).databaseId === databaseId && selectedNode.id() === collectionId
|
||||
: selectedNode.id() === databaseId;
|
||||
|
||||
if (!isNodeSelected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!subnodeKind) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const activeTab = this.container.tabsManager.activeTab();
|
||||
const selectedSubnodeKind = collectionId
|
||||
? (selectedNode as ViewModels.Collection).selectedSubnodeKind()
|
||||
: (selectedNode as ViewModels.Database).selectedSubnodeKind();
|
||||
|
||||
return activeTab && activeTab.tabKind === subnodeKind && selectedSubnodeKind === subnodeKind;
|
||||
}
|
||||
|
||||
public triggerRender() {
|
||||
window.requestAnimationFrame(() => this.parameters(Date.now()));
|
||||
}
|
||||
|
||||
@@ -3,14 +3,16 @@ import * as ko from "knockout";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { deleteStoredProcedure } from "../../Common/dataAccess/deleteStoredProcedure";
|
||||
import { executeStoredProcedure } from "../../Common/dataAccess/executeStoredProcedure";
|
||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../UserContext";
|
||||
import Explorer from "../Explorer";
|
||||
import StoredProcedureTab from "../Tabs/StoredProcedureTab";
|
||||
import { getErrorMessage } from "../Tables/Utilities";
|
||||
import { NewStoredProcedureTab } from "../Tabs/StoredProcedureTab/StoredProcedureTab";
|
||||
import TabsBase from "../Tabs/TabsBase";
|
||||
import { useSelectedNode } from "../useSelectedNode";
|
||||
|
||||
const sampleStoredProcedureBody: string = `// SAMPLE STORED PROCEDURE
|
||||
function sample(prefix) {
|
||||
@@ -61,28 +63,33 @@ export default class StoredProcedure {
|
||||
}
|
||||
|
||||
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>{
|
||||
id: "",
|
||||
body: sampleStoredProcedureBody,
|
||||
};
|
||||
|
||||
const storedProcedureTab: StoredProcedureTab = new StoredProcedureTab({
|
||||
resource: storedProcedure,
|
||||
isNew: true,
|
||||
tabKind: ViewModels.CollectionTabKind.StoredProcedures,
|
||||
title: `New Stored Procedure ${id}`,
|
||||
tabPath: `${source.databaseId}>${source.id()}>New Stored Procedure ${id}`,
|
||||
collection: source,
|
||||
node: source,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/sproc`,
|
||||
});
|
||||
const storedProcedureTab: NewStoredProcedureTab = new NewStoredProcedureTab(
|
||||
{
|
||||
resource: storedProcedure,
|
||||
isNew: true,
|
||||
tabKind: ViewModels.CollectionTabKind.StoredProcedures,
|
||||
title: `New Stored Procedure ${id}`,
|
||||
tabPath: `${source.databaseId}>${source.id()}>New Stored Procedure ${id}`,
|
||||
collection: source,
|
||||
node: source,
|
||||
},
|
||||
{
|
||||
collection: source,
|
||||
container: source.container,
|
||||
}
|
||||
);
|
||||
|
||||
source.container.tabsManager.activateNewTab(storedProcedureTab);
|
||||
useTabs.getState().activateNewTab(storedProcedureTab);
|
||||
}
|
||||
|
||||
public select() {
|
||||
this.container.selectedNode(this);
|
||||
useSelectedNode.getState().setSelectedNode(this);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Stored procedure node",
|
||||
|
||||
@@ -93,14 +100,16 @@ export default class StoredProcedure {
|
||||
public open = () => {
|
||||
this.select();
|
||||
|
||||
const storedProcedureTabs: StoredProcedureTab[] = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.StoredProcedures,
|
||||
(tab: TabsBase) => tab.node && tab.node.rid === this.rid
|
||||
) as StoredProcedureTab[];
|
||||
let storedProcedureTab: StoredProcedureTab = storedProcedureTabs && storedProcedureTabs[0];
|
||||
const storedProcedureTabs: NewStoredProcedureTab[] = useTabs
|
||||
.getState()
|
||||
.getTabs(
|
||||
ViewModels.CollectionTabKind.StoredProcedures,
|
||||
(tab: TabsBase) => tab.node && tab.node.rid === this.rid
|
||||
) as NewStoredProcedureTab[];
|
||||
let storedProcedureTab: NewStoredProcedureTab = storedProcedureTabs && storedProcedureTabs[0];
|
||||
|
||||
if (storedProcedureTab) {
|
||||
this.container.tabsManager.activateTab(storedProcedureTab);
|
||||
useTabs.getState().activateTab(storedProcedureTab);
|
||||
} else {
|
||||
const storedProcedureData = <StoredProcedureDefinition>{
|
||||
_rid: this.rid,
|
||||
@@ -109,24 +118,25 @@ export default class StoredProcedure {
|
||||
body: this.body(),
|
||||
};
|
||||
|
||||
storedProcedureTab = new StoredProcedureTab({
|
||||
resource: storedProcedureData,
|
||||
isNew: false,
|
||||
tabKind: ViewModels.CollectionTabKind.StoredProcedures,
|
||||
title: storedProcedureData.id,
|
||||
tabPath: `${this.collection.databaseId}>${this.collection.id()}>${storedProcedureData.id}`,
|
||||
collection: this.collection,
|
||||
node: this,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(
|
||||
this.collection.databaseId,
|
||||
this.collection.id()
|
||||
)}/sprocs/${this.id()}`,
|
||||
});
|
||||
storedProcedureTab = new NewStoredProcedureTab(
|
||||
{
|
||||
resource: storedProcedureData,
|
||||
isNew: false,
|
||||
tabKind: ViewModels.CollectionTabKind.StoredProcedures,
|
||||
title: storedProcedureData.id,
|
||||
tabPath: `${this.collection.databaseId}>${this.collection.id()}>${storedProcedureData.id}`,
|
||||
collection: this.collection,
|
||||
node: this,
|
||||
},
|
||||
{
|
||||
collection: this.collection,
|
||||
container: this.container,
|
||||
}
|
||||
);
|
||||
|
||||
this.container.tabsManager.activateNewTab(storedProcedureTab);
|
||||
useTabs.getState().activateNewTab(storedProcedureTab);
|
||||
}
|
||||
};
|
||||
|
||||
public delete() {
|
||||
if (!window.confirm("Are you sure you want to delete the stored procedure?")) {
|
||||
return;
|
||||
@@ -134,7 +144,7 @@ export default class StoredProcedure {
|
||||
|
||||
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);
|
||||
},
|
||||
(reason) => {}
|
||||
@@ -142,19 +152,21 @@ export default class StoredProcedure {
|
||||
}
|
||||
|
||||
public execute(params: string[], partitionKeyValue?: string): void {
|
||||
const sprocTabs = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.StoredProcedures,
|
||||
(tab: TabsBase) => tab.node && tab.node.rid === this.rid
|
||||
) as StoredProcedureTab[];
|
||||
const sprocTab = sprocTabs && sprocTabs.length > 0 && sprocTabs[0];
|
||||
const sprocTabs: NewStoredProcedureTab[] = useTabs
|
||||
.getState()
|
||||
.getTabs(
|
||||
ViewModels.CollectionTabKind.StoredProcedures,
|
||||
(tab: TabsBase) => tab.node && tab.node.rid === this.rid
|
||||
) as NewStoredProcedureTab[];
|
||||
const sprocTab: NewStoredProcedureTab = sprocTabs && sprocTabs.length > 0 && sprocTabs[0];
|
||||
sprocTab.isExecuting(true);
|
||||
this.container &&
|
||||
executeStoredProcedure(this.collection, this, partitionKeyValue, params)
|
||||
.then(
|
||||
(result: any) => {
|
||||
sprocTab.onExecuteSprocsResult(result, result.scriptLogs);
|
||||
(result) => {
|
||||
sprocTab.onExecuteSprocsResult(result);
|
||||
},
|
||||
(error: any) => {
|
||||
(error) => {
|
||||
sprocTab.onExecuteSprocsError(getErrorMessage(error));
|
||||
}
|
||||
)
|
||||
|
||||
@@ -3,10 +3,12 @@ import * as ko from "knockout";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { deleteTrigger } from "../../Common/dataAccess/deleteTrigger";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import Explorer from "../Explorer";
|
||||
import TriggerTab from "../Tabs/TriggerTab";
|
||||
import { useSelectedNode } from "../useSelectedNode";
|
||||
|
||||
export default class Trigger {
|
||||
public nodeKind: string;
|
||||
@@ -32,7 +34,7 @@ export default class Trigger {
|
||||
}
|
||||
|
||||
public select() {
|
||||
this.container.selectedNode(this);
|
||||
useSelectedNode.getState().setSelectedNode(this);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Trigger node",
|
||||
|
||||
@@ -41,7 +43,7 @@ export default class Trigger {
|
||||
}
|
||||
|
||||
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>{
|
||||
id: "",
|
||||
body: "function trigger(){}",
|
||||
@@ -57,23 +59,21 @@ export default class Trigger {
|
||||
tabPath: "",
|
||||
collection: source,
|
||||
node: source,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/trigger`,
|
||||
});
|
||||
|
||||
source.container.tabsManager.activateNewTab(triggerTab);
|
||||
useTabs.getState().activateNewTab(triggerTab);
|
||||
}
|
||||
|
||||
public open = () => {
|
||||
this.select();
|
||||
|
||||
const triggerTabs: TriggerTab[] = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.Triggers,
|
||||
(tab) => tab.node && tab.node.rid === this.rid
|
||||
) as TriggerTab[];
|
||||
const triggerTabs: TriggerTab[] = useTabs
|
||||
.getState()
|
||||
.getTabs(ViewModels.CollectionTabKind.Triggers, (tab) => tab.node && tab.node.rid === this.rid) as TriggerTab[];
|
||||
let triggerTab: TriggerTab = triggerTabs && triggerTabs[0];
|
||||
|
||||
if (triggerTab) {
|
||||
this.container.tabsManager.activateTab(triggerTab);
|
||||
useTabs.getState().activateTab(triggerTab);
|
||||
} else {
|
||||
const triggerData = <StoredProcedureDefinition>{
|
||||
_rid: this.rid,
|
||||
@@ -92,13 +92,9 @@ export default class Trigger {
|
||||
tabPath: "",
|
||||
collection: this.collection,
|
||||
node: this,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(
|
||||
this.collection.databaseId,
|
||||
this.collection.id()
|
||||
)}/triggers/${this.id()}`,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(triggerTab);
|
||||
useTabs.getState().activateNewTab(triggerTab);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -109,7 +105,7 @@ export default class Trigger {
|
||||
|
||||
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);
|
||||
},
|
||||
(reason) => {}
|
||||
|
||||
@@ -3,10 +3,12 @@ import * as ko from "knockout";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { deleteUserDefinedFunction } from "../../Common/dataAccess/deleteUserDefinedFunction";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import Explorer from "../Explorer";
|
||||
import UserDefinedFunctionTab from "../Tabs/UserDefinedFunctionTab";
|
||||
import { useSelectedNode } from "../useSelectedNode";
|
||||
|
||||
export default class UserDefinedFunction {
|
||||
public nodeKind: string;
|
||||
@@ -29,7 +31,7 @@ export default class UserDefinedFunction {
|
||||
}
|
||||
|
||||
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 = {
|
||||
id: "",
|
||||
body: "function userDefinedFunction(){}",
|
||||
@@ -43,23 +45,24 @@ export default class UserDefinedFunction {
|
||||
tabPath: "",
|
||||
collection: source,
|
||||
node: source,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/udf`,
|
||||
});
|
||||
|
||||
source.container.tabsManager.activateNewTab(userDefinedFunctionTab);
|
||||
useTabs.getState().activateNewTab(userDefinedFunctionTab);
|
||||
}
|
||||
|
||||
public open = () => {
|
||||
this.select();
|
||||
|
||||
const userDefinedFunctionTabs: UserDefinedFunctionTab[] = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.UserDefinedFunctions,
|
||||
(tab) => tab.node?.rid === this.rid
|
||||
) as UserDefinedFunctionTab[];
|
||||
const userDefinedFunctionTabs: UserDefinedFunctionTab[] = useTabs
|
||||
.getState()
|
||||
.getTabs(
|
||||
ViewModels.CollectionTabKind.UserDefinedFunctions,
|
||||
(tab) => tab.node?.rid === this.rid
|
||||
) as UserDefinedFunctionTab[];
|
||||
let userDefinedFunctionTab: UserDefinedFunctionTab = userDefinedFunctionTabs && userDefinedFunctionTabs[0];
|
||||
|
||||
if (userDefinedFunctionTab) {
|
||||
this.container.tabsManager.activateTab(userDefinedFunctionTab);
|
||||
useTabs.getState().activateTab(userDefinedFunctionTab);
|
||||
} else {
|
||||
const userDefinedFunctionData = {
|
||||
_rid: this.rid,
|
||||
@@ -76,18 +79,14 @@ export default class UserDefinedFunction {
|
||||
tabPath: "",
|
||||
collection: this.collection,
|
||||
node: this,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(
|
||||
this.collection.databaseId,
|
||||
this.collection.id()
|
||||
)}/udfs/${this.id()}`,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(userDefinedFunctionTab);
|
||||
useTabs.getState().activateNewTab(userDefinedFunctionTab);
|
||||
}
|
||||
};
|
||||
|
||||
public select() {
|
||||
this.container.selectedNode(this);
|
||||
useSelectedNode.getState().setSelectedNode(this);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "UDF item node",
|
||||
|
||||
@@ -102,7 +101,7 @@ export default class UserDefinedFunction {
|
||||
|
||||
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);
|
||||
},
|
||||
(reason) => {}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
exports[`Resource tree for schema should render 1`] = `
|
||||
<div
|
||||
className="treeComponent dataResourceTree"
|
||||
role="tree"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
generation={0}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
exports[`Resource tree for resource token should render 1`] = `
|
||||
<div
|
||||
className="treeComponent dataResourceTree"
|
||||
role="tree"
|
||||
>
|
||||
<TreeNodeComponent
|
||||
generation={0}
|
||||
|
||||
Reference in New Issue
Block a user