Merge branch 'master'

This commit is contained in:
sunilyadav840
2021-07-12 01:26:43 +05:30
146 changed files with 6543 additions and 6209 deletions

View File

@@ -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);
}

View File

@@ -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> {

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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();
});
});

View File

@@ -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", () => {

View File

@@ -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);
}
}

View File

@@ -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();

View File

@@ -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()));
}

View File

@@ -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));
}
)

View File

@@ -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) => {}

View File

@@ -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) => {}

View File

@@ -3,6 +3,7 @@
exports[`Resource tree for schema should render 1`] = `
<div
className="treeComponent dataResourceTree"
role="tree"
>
<TreeNodeComponent
generation={0}

View File

@@ -3,6 +3,7 @@
exports[`Resource tree for resource token should render 1`] = `
<div
className="treeComponent dataResourceTree"
role="tree"
>
<TreeNodeComponent
generation={0}