mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-19 08:51:24 +00:00
Prettier 2.0 (#393)
This commit is contained in:
@@ -1,123 +1,123 @@
|
||||
import * as _ from "underscore";
|
||||
import * as ko from "knockout";
|
||||
|
||||
enum ScrollPosition {
|
||||
Top,
|
||||
Bottom
|
||||
}
|
||||
|
||||
export class AccessibleVerticalList {
|
||||
private items: any[] = [];
|
||||
private onSelect?: (item: any) => void;
|
||||
|
||||
public currentItemIndex: ko.Observable<number>;
|
||||
public currentItem: ko.Computed<any>;
|
||||
|
||||
constructor(initialSetOfItems: any[]) {
|
||||
this.items = initialSetOfItems;
|
||||
this.currentItemIndex = this.items != null && this.items.length > 0 ? ko.observable(0) : ko.observable(-1);
|
||||
this.currentItem = ko.computed<any>(() => this.items[this.currentItemIndex()]);
|
||||
}
|
||||
|
||||
public setOnSelect(onSelect: (item: any) => void): void {
|
||||
this.onSelect = onSelect;
|
||||
}
|
||||
|
||||
public onKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
const targetContainer: Element = <Element>event.target;
|
||||
if (this.items == null || this.items.length === 0) {
|
||||
// no items so this should be a noop
|
||||
return true;
|
||||
}
|
||||
if (event.keyCode === 32 || event.keyCode === 13) {
|
||||
// on space or enter keydown
|
||||
this.onSelect && this.onSelect(this.currentItem());
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
if (event.keyCode === 38) {
|
||||
// on UpArrow keydown
|
||||
event.preventDefault();
|
||||
this.selectPreviousItem();
|
||||
const targetElement = targetContainer
|
||||
.getElementsByClassName("accessibleListElement")
|
||||
.item(this.currentItemIndex());
|
||||
if (targetElement) {
|
||||
this.scrollElementIntoContainerViewIfNeeded(targetElement, targetContainer, ScrollPosition.Top);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (event.keyCode === 40) {
|
||||
// on DownArrow keydown
|
||||
event.preventDefault();
|
||||
this.selectNextItem();
|
||||
const targetElement = targetContainer
|
||||
.getElementsByClassName("accessibleListElement")
|
||||
.item(this.currentItemIndex());
|
||||
if (targetElement) {
|
||||
this.scrollElementIntoContainerViewIfNeeded(targetElement, targetContainer, ScrollPosition.Top);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
public updateItemList(newItemList: any[]) {
|
||||
if (newItemList == null || newItemList.length === 0) {
|
||||
this.currentItemIndex(-1);
|
||||
this.items = [];
|
||||
return;
|
||||
} else if (this.currentItemIndex() < 0) {
|
||||
this.currentItemIndex(0);
|
||||
}
|
||||
this.items = newItemList;
|
||||
}
|
||||
|
||||
public updateCurrentItem(item: any) {
|
||||
const updatedIndex: number = this.isItemListEmpty() ? -1 : _.indexOf(this.items, item);
|
||||
this.currentItemIndex(updatedIndex);
|
||||
}
|
||||
|
||||
private isElementVisibleInContainer(element: Element, container: Element): boolean {
|
||||
const elementTop = element.getBoundingClientRect().top;
|
||||
const elementBottom = element.getBoundingClientRect().bottom;
|
||||
const containerTop = container.getBoundingClientRect().top;
|
||||
const containerBottom = container.getBoundingClientRect().bottom;
|
||||
|
||||
return elementTop >= containerTop && elementBottom <= containerBottom;
|
||||
}
|
||||
|
||||
private scrollElementIntoContainerViewIfNeeded(
|
||||
element: Element,
|
||||
container: Element,
|
||||
scrollPosition: ScrollPosition
|
||||
): void {
|
||||
if (!this.isElementVisibleInContainer(element, container)) {
|
||||
if (scrollPosition === ScrollPosition.Top) {
|
||||
container.scrollTop =
|
||||
element.getBoundingClientRect().top - container.getBoundingClientRect().top + container.scrollTop;
|
||||
} else {
|
||||
container.scrollTop =
|
||||
element.getBoundingClientRect().bottom - element.getBoundingClientRect().top + container.scrollTop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private selectPreviousItem(): void {
|
||||
if (this.currentItemIndex() <= 0 || this.isItemListEmpty()) {
|
||||
return;
|
||||
}
|
||||
this.currentItemIndex(this.currentItemIndex() - 1);
|
||||
}
|
||||
|
||||
private selectNextItem(): void {
|
||||
if (this.isItemListEmpty() || this.currentItemIndex() === this.items.length - 1) {
|
||||
return;
|
||||
}
|
||||
this.currentItemIndex(this.currentItemIndex() + 1);
|
||||
}
|
||||
|
||||
private isItemListEmpty(): boolean {
|
||||
return this.items == null || this.items.length === 0;
|
||||
}
|
||||
}
|
||||
import * as _ from "underscore";
|
||||
import * as ko from "knockout";
|
||||
|
||||
enum ScrollPosition {
|
||||
Top,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
export class AccessibleVerticalList {
|
||||
private items: any[] = [];
|
||||
private onSelect?: (item: any) => void;
|
||||
|
||||
public currentItemIndex: ko.Observable<number>;
|
||||
public currentItem: ko.Computed<any>;
|
||||
|
||||
constructor(initialSetOfItems: any[]) {
|
||||
this.items = initialSetOfItems;
|
||||
this.currentItemIndex = this.items != null && this.items.length > 0 ? ko.observable(0) : ko.observable(-1);
|
||||
this.currentItem = ko.computed<any>(() => this.items[this.currentItemIndex()]);
|
||||
}
|
||||
|
||||
public setOnSelect(onSelect: (item: any) => void): void {
|
||||
this.onSelect = onSelect;
|
||||
}
|
||||
|
||||
public onKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
const targetContainer: Element = <Element>event.target;
|
||||
if (this.items == null || this.items.length === 0) {
|
||||
// no items so this should be a noop
|
||||
return true;
|
||||
}
|
||||
if (event.keyCode === 32 || event.keyCode === 13) {
|
||||
// on space or enter keydown
|
||||
this.onSelect && this.onSelect(this.currentItem());
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
if (event.keyCode === 38) {
|
||||
// on UpArrow keydown
|
||||
event.preventDefault();
|
||||
this.selectPreviousItem();
|
||||
const targetElement = targetContainer
|
||||
.getElementsByClassName("accessibleListElement")
|
||||
.item(this.currentItemIndex());
|
||||
if (targetElement) {
|
||||
this.scrollElementIntoContainerViewIfNeeded(targetElement, targetContainer, ScrollPosition.Top);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (event.keyCode === 40) {
|
||||
// on DownArrow keydown
|
||||
event.preventDefault();
|
||||
this.selectNextItem();
|
||||
const targetElement = targetContainer
|
||||
.getElementsByClassName("accessibleListElement")
|
||||
.item(this.currentItemIndex());
|
||||
if (targetElement) {
|
||||
this.scrollElementIntoContainerViewIfNeeded(targetElement, targetContainer, ScrollPosition.Top);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
public updateItemList(newItemList: any[]) {
|
||||
if (newItemList == null || newItemList.length === 0) {
|
||||
this.currentItemIndex(-1);
|
||||
this.items = [];
|
||||
return;
|
||||
} else if (this.currentItemIndex() < 0) {
|
||||
this.currentItemIndex(0);
|
||||
}
|
||||
this.items = newItemList;
|
||||
}
|
||||
|
||||
public updateCurrentItem(item: any) {
|
||||
const updatedIndex: number = this.isItemListEmpty() ? -1 : _.indexOf(this.items, item);
|
||||
this.currentItemIndex(updatedIndex);
|
||||
}
|
||||
|
||||
private isElementVisibleInContainer(element: Element, container: Element): boolean {
|
||||
const elementTop = element.getBoundingClientRect().top;
|
||||
const elementBottom = element.getBoundingClientRect().bottom;
|
||||
const containerTop = container.getBoundingClientRect().top;
|
||||
const containerBottom = container.getBoundingClientRect().bottom;
|
||||
|
||||
return elementTop >= containerTop && elementBottom <= containerBottom;
|
||||
}
|
||||
|
||||
private scrollElementIntoContainerViewIfNeeded(
|
||||
element: Element,
|
||||
container: Element,
|
||||
scrollPosition: ScrollPosition
|
||||
): void {
|
||||
if (!this.isElementVisibleInContainer(element, container)) {
|
||||
if (scrollPosition === ScrollPosition.Top) {
|
||||
container.scrollTop =
|
||||
element.getBoundingClientRect().top - container.getBoundingClientRect().top + container.scrollTop;
|
||||
} else {
|
||||
container.scrollTop =
|
||||
element.getBoundingClientRect().bottom - element.getBoundingClientRect().top + container.scrollTop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private selectPreviousItem(): void {
|
||||
if (this.currentItemIndex() <= 0 || this.isItemListEmpty()) {
|
||||
return;
|
||||
}
|
||||
this.currentItemIndex(this.currentItemIndex() - 1);
|
||||
}
|
||||
|
||||
private selectNextItem(): void {
|
||||
if (this.isItemListEmpty() || this.currentItemIndex() === this.items.length - 1) {
|
||||
return;
|
||||
}
|
||||
this.currentItemIndex(this.currentItemIndex() + 1);
|
||||
}
|
||||
|
||||
private isItemListEmpty(): boolean {
|
||||
return this.items == null || this.items.length === 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,112 +1,112 @@
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ko from "knockout";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import Collection from "./Collection";
|
||||
import Explorer from "../Explorer";
|
||||
jest.mock("monaco-editor");
|
||||
|
||||
describe("Collection", () => {
|
||||
function generateCollection(
|
||||
container: Explorer,
|
||||
databaseId: string,
|
||||
data: DataModels.Collection,
|
||||
offer: DataModels.Offer
|
||||
): Collection {
|
||||
return new Collection(container, databaseId, data);
|
||||
}
|
||||
|
||||
function generateMockCollectionsDataModelWithPartitionKey(
|
||||
partitionKey: DataModels.PartitionKey
|
||||
): DataModels.Collection {
|
||||
return {
|
||||
defaultTtl: 1,
|
||||
indexingPolicy: {} as DataModels.IndexingPolicy,
|
||||
partitionKey,
|
||||
_rid: "",
|
||||
_self: "",
|
||||
_etag: "",
|
||||
_ts: 1,
|
||||
id: ""
|
||||
};
|
||||
}
|
||||
|
||||
function generateMockCollectionWithDataModel(data: DataModels.Collection): Collection {
|
||||
const mockContainer = {} as Explorer;
|
||||
mockContainer.isPreferredApiMongoDB = ko.computed(() => {
|
||||
return false;
|
||||
});
|
||||
mockContainer.isPreferredApiCassandra = ko.computed(() => {
|
||||
return false;
|
||||
});
|
||||
mockContainer.isDatabaseNodeOrNoneSelected = () => {
|
||||
return false;
|
||||
};
|
||||
mockContainer.isPreferredApiDocumentDB = ko.computed(() => {
|
||||
return true;
|
||||
});
|
||||
mockContainer.isPreferredApiGraph = ko.computed(() => {
|
||||
return false;
|
||||
});
|
||||
mockContainer.deleteCollectionText = ko.observable<string>("delete collection");
|
||||
|
||||
return generateCollection(mockContainer, "abc", data, {} as DataModels.Offer);
|
||||
}
|
||||
|
||||
describe("Partition key path parsing", () => {
|
||||
let collection: Collection;
|
||||
|
||||
it("should strip out multiple forward slashes from partition key paths", () => {
|
||||
const collectionsDataModel = generateMockCollectionsDataModelWithPartitionKey({
|
||||
paths: ["/somePartitionKey/anotherPartitionKey"],
|
||||
kind: "Hash",
|
||||
version: 2
|
||||
});
|
||||
collection = generateMockCollectionWithDataModel(collectionsDataModel);
|
||||
expect(collection.partitionKeyProperty).toBe("somePartitionKey.anotherPartitionKey");
|
||||
});
|
||||
|
||||
it("should strip out forward slashes from single partition key paths", () => {
|
||||
const collectionsDataModel = generateMockCollectionsDataModelWithPartitionKey({
|
||||
paths: ["/somePartitionKey"],
|
||||
kind: "Hash",
|
||||
version: 2
|
||||
});
|
||||
collection = generateMockCollectionWithDataModel(collectionsDataModel);
|
||||
expect(collection.partitionKeyProperty).toBe("somePartitionKey");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Partition key path header", () => {
|
||||
let collection: Collection;
|
||||
|
||||
it("should preserve forward slashes on partition keys", () => {
|
||||
const collectionsDataModel = generateMockCollectionsDataModelWithPartitionKey({
|
||||
paths: ["/somePartitionKey/anotherPartitionKey"],
|
||||
kind: "Hash",
|
||||
version: 2
|
||||
});
|
||||
collection = generateMockCollectionWithDataModel(collectionsDataModel);
|
||||
expect(collection.partitionKeyPropertyHeader).toBe("/somePartitionKey/anotherPartitionKey");
|
||||
});
|
||||
|
||||
it("should preserve forward slash on a single partition key", () => {
|
||||
const collectionsDataModel = generateMockCollectionsDataModelWithPartitionKey({
|
||||
paths: ["/somePartitionKey"],
|
||||
kind: "Hash",
|
||||
version: 2
|
||||
});
|
||||
collection = generateMockCollectionWithDataModel(collectionsDataModel);
|
||||
expect(collection.partitionKeyPropertyHeader).toBe("/somePartitionKey");
|
||||
});
|
||||
|
||||
it("should be null if there is no partition key", () => {
|
||||
const collectionsDataModel = generateMockCollectionsDataModelWithPartitionKey({
|
||||
version: 2,
|
||||
paths: [],
|
||||
kind: "Hash"
|
||||
});
|
||||
collection = generateMockCollectionWithDataModel(collectionsDataModel);
|
||||
expect(collection.partitionKeyPropertyHeader).toBeNull;
|
||||
});
|
||||
});
|
||||
});
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ko from "knockout";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import Collection from "./Collection";
|
||||
import Explorer from "../Explorer";
|
||||
jest.mock("monaco-editor");
|
||||
|
||||
describe("Collection", () => {
|
||||
function generateCollection(
|
||||
container: Explorer,
|
||||
databaseId: string,
|
||||
data: DataModels.Collection,
|
||||
offer: DataModels.Offer
|
||||
): Collection {
|
||||
return new Collection(container, databaseId, data);
|
||||
}
|
||||
|
||||
function generateMockCollectionsDataModelWithPartitionKey(
|
||||
partitionKey: DataModels.PartitionKey
|
||||
): DataModels.Collection {
|
||||
return {
|
||||
defaultTtl: 1,
|
||||
indexingPolicy: {} as DataModels.IndexingPolicy,
|
||||
partitionKey,
|
||||
_rid: "",
|
||||
_self: "",
|
||||
_etag: "",
|
||||
_ts: 1,
|
||||
id: "",
|
||||
};
|
||||
}
|
||||
|
||||
function generateMockCollectionWithDataModel(data: DataModels.Collection): Collection {
|
||||
const mockContainer = {} as Explorer;
|
||||
mockContainer.isPreferredApiMongoDB = ko.computed(() => {
|
||||
return false;
|
||||
});
|
||||
mockContainer.isPreferredApiCassandra = ko.computed(() => {
|
||||
return false;
|
||||
});
|
||||
mockContainer.isDatabaseNodeOrNoneSelected = () => {
|
||||
return false;
|
||||
};
|
||||
mockContainer.isPreferredApiDocumentDB = ko.computed(() => {
|
||||
return true;
|
||||
});
|
||||
mockContainer.isPreferredApiGraph = ko.computed(() => {
|
||||
return false;
|
||||
});
|
||||
mockContainer.deleteCollectionText = ko.observable<string>("delete collection");
|
||||
|
||||
return generateCollection(mockContainer, "abc", data, {} as DataModels.Offer);
|
||||
}
|
||||
|
||||
describe("Partition key path parsing", () => {
|
||||
let collection: Collection;
|
||||
|
||||
it("should strip out multiple forward slashes from partition key paths", () => {
|
||||
const collectionsDataModel = generateMockCollectionsDataModelWithPartitionKey({
|
||||
paths: ["/somePartitionKey/anotherPartitionKey"],
|
||||
kind: "Hash",
|
||||
version: 2,
|
||||
});
|
||||
collection = generateMockCollectionWithDataModel(collectionsDataModel);
|
||||
expect(collection.partitionKeyProperty).toBe("somePartitionKey.anotherPartitionKey");
|
||||
});
|
||||
|
||||
it("should strip out forward slashes from single partition key paths", () => {
|
||||
const collectionsDataModel = generateMockCollectionsDataModelWithPartitionKey({
|
||||
paths: ["/somePartitionKey"],
|
||||
kind: "Hash",
|
||||
version: 2,
|
||||
});
|
||||
collection = generateMockCollectionWithDataModel(collectionsDataModel);
|
||||
expect(collection.partitionKeyProperty).toBe("somePartitionKey");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Partition key path header", () => {
|
||||
let collection: Collection;
|
||||
|
||||
it("should preserve forward slashes on partition keys", () => {
|
||||
const collectionsDataModel = generateMockCollectionsDataModelWithPartitionKey({
|
||||
paths: ["/somePartitionKey/anotherPartitionKey"],
|
||||
kind: "Hash",
|
||||
version: 2,
|
||||
});
|
||||
collection = generateMockCollectionWithDataModel(collectionsDataModel);
|
||||
expect(collection.partitionKeyPropertyHeader).toBe("/somePartitionKey/anotherPartitionKey");
|
||||
});
|
||||
|
||||
it("should preserve forward slash on a single partition key", () => {
|
||||
const collectionsDataModel = generateMockCollectionsDataModelWithPartitionKey({
|
||||
paths: ["/somePartitionKey"],
|
||||
kind: "Hash",
|
||||
version: 2,
|
||||
});
|
||||
collection = generateMockCollectionWithDataModel(collectionsDataModel);
|
||||
expect(collection.partitionKeyPropertyHeader).toBe("/somePartitionKey");
|
||||
});
|
||||
|
||||
it("should be null if there is no partition key", () => {
|
||||
const collectionsDataModel = generateMockCollectionsDataModelWithPartitionKey({
|
||||
version: 2,
|
||||
paths: [],
|
||||
kind: "Hash",
|
||||
});
|
||||
collection = generateMockCollectionWithDataModel(collectionsDataModel);
|
||||
expect(collection.partitionKeyPropertyHeader).toBeNull;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -122,10 +122,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
this.partitionKey.paths &&
|
||||
this.partitionKey.paths.length &&
|
||||
this.partitionKey.paths.length > 0 &&
|
||||
this.partitionKey.paths[0]
|
||||
.replace(/[/]+/g, ".")
|
||||
.substr(1)
|
||||
.replace(/[']+/g, "")) ||
|
||||
this.partitionKey.paths[0].replace(/[/]+/g, ".").substr(1).replace(/[']+/g, "")) ||
|
||||
null;
|
||||
this.partitionKeyPropertyHeader =
|
||||
(this.partitionKey &&
|
||||
@@ -155,28 +152,28 @@ export default class Collection implements ViewModels.Collection {
|
||||
this.focusedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
|
||||
|
||||
this.documentsFocused = ko.observable<boolean>();
|
||||
this.documentsFocused.subscribe(focus => {
|
||||
this.documentsFocused.subscribe((focus) => {
|
||||
console.log("Focus set on Documents: " + focus);
|
||||
this.focusedSubnodeKind(ViewModels.CollectionTabKind.Documents);
|
||||
});
|
||||
|
||||
this.settingsFocused = ko.observable<boolean>(false);
|
||||
this.settingsFocused.subscribe(focus => {
|
||||
this.settingsFocused.subscribe((focus) => {
|
||||
this.focusedSubnodeKind(ViewModels.CollectionTabKind.Settings);
|
||||
});
|
||||
|
||||
this.storedProceduresFocused = ko.observable<boolean>(false);
|
||||
this.storedProceduresFocused.subscribe(focus => {
|
||||
this.storedProceduresFocused.subscribe((focus) => {
|
||||
this.focusedSubnodeKind(ViewModels.CollectionTabKind.StoredProcedures);
|
||||
});
|
||||
|
||||
this.userDefinedFunctionsFocused = ko.observable<boolean>(false);
|
||||
this.userDefinedFunctionsFocused.subscribe(focus => {
|
||||
this.userDefinedFunctionsFocused.subscribe((focus) => {
|
||||
this.focusedSubnodeKind(ViewModels.CollectionTabKind.UserDefinedFunctions);
|
||||
});
|
||||
|
||||
this.triggersFocused = ko.observable<boolean>(false);
|
||||
this.triggersFocused.subscribe(focus => {
|
||||
this.triggersFocused.subscribe((focus) => {
|
||||
this.focusedSubnodeKind(ViewModels.CollectionTabKind.Triggers);
|
||||
});
|
||||
|
||||
@@ -184,20 +181,20 @@ export default class Collection implements ViewModels.Collection {
|
||||
|
||||
this.storedProcedures = ko.computed(() => {
|
||||
return this.children()
|
||||
.filter(node => node.nodeKind === "StoredProcedure")
|
||||
.map(node => <StoredProcedure>node);
|
||||
.filter((node) => node.nodeKind === "StoredProcedure")
|
||||
.map((node) => <StoredProcedure>node);
|
||||
});
|
||||
|
||||
this.userDefinedFunctions = ko.computed(() => {
|
||||
return this.children()
|
||||
.filter(node => node.nodeKind === "UserDefinedFunction")
|
||||
.map(node => <UserDefinedFunction>node);
|
||||
.filter((node) => node.nodeKind === "UserDefinedFunction")
|
||||
.map((node) => <UserDefinedFunction>node);
|
||||
});
|
||||
|
||||
this.triggers = ko.computed(() => {
|
||||
return this.children()
|
||||
.filter(node => node.nodeKind === "Trigger")
|
||||
.map(node => <Trigger>node);
|
||||
.filter((node) => node.nodeKind === "Trigger")
|
||||
.map((node) => <Trigger>node);
|
||||
});
|
||||
|
||||
const showScriptsMenus: boolean = container.isPreferredApiDocumentDB() || container.isPreferredApiGraph();
|
||||
@@ -228,7 +225,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
if (this.isCollectionExpanded()) {
|
||||
this.collapseCollection();
|
||||
@@ -237,7 +234,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
}
|
||||
this.container.onUpdateTabsButtons([]);
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -253,7 +250,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -269,7 +266,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
|
||||
return Q.resolve();
|
||||
@@ -284,12 +281,12 @@ export default class Collection implements ViewModels.Collection {
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
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()
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as DocumentsTab[];
|
||||
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
|
||||
|
||||
@@ -302,7 +299,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: "Items"
|
||||
tabTitle: "Items",
|
||||
});
|
||||
this.documentIds([]);
|
||||
|
||||
@@ -317,7 +314,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
tabPath: `${this.databaseId}>${this.id()}>Documents`,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/documents`,
|
||||
onLoadStartKey: startKey,
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(documentsTab);
|
||||
@@ -333,12 +330,12 @@ export default class Collection implements ViewModels.Collection {
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
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()
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as ConflictsTab[];
|
||||
let conflictsTab: ConflictsTab = conflictsTabs && conflictsTabs[0];
|
||||
|
||||
@@ -351,7 +348,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: "Conflicts"
|
||||
tabTitle: "Conflicts",
|
||||
});
|
||||
this.documentIds([]);
|
||||
|
||||
@@ -366,7 +363,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
tabPath: `${this.databaseId}>${this.id()}>Conflicts`,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/conflicts`,
|
||||
onLoadStartKey: startKey,
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(conflictsTab);
|
||||
@@ -382,7 +379,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
|
||||
if (this.container.isPreferredApiCassandra() && !this.cassandraKeys) {
|
||||
@@ -393,7 +390,7 @@ 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()
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as QueryTablesTab[];
|
||||
let queryTablesTab: QueryTablesTab = queryTablesTabs && queryTablesTabs[0];
|
||||
|
||||
@@ -411,7 +408,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: title
|
||||
tabTitle: title,
|
||||
});
|
||||
|
||||
queryTablesTab = new QueryTablesTab({
|
||||
@@ -425,7 +422,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/entities`,
|
||||
isActive: ko.observable(false),
|
||||
onLoadStartKey: startKey,
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(queryTablesTab);
|
||||
@@ -441,12 +438,12 @@ export default class Collection implements ViewModels.Collection {
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
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()
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as GraphTab[];
|
||||
let graphTab: GraphTab = graphTabs && graphTabs[0];
|
||||
|
||||
@@ -461,7 +458,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: title
|
||||
tabTitle: title,
|
||||
});
|
||||
|
||||
graphTab = new GraphTab({
|
||||
@@ -480,7 +477,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
databaseId: this.databaseId,
|
||||
isTabsContentExpanded: this.container.isTabsContentExpanded,
|
||||
onLoadStartKey: startKey,
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(graphTab);
|
||||
@@ -496,12 +493,12 @@ export default class Collection implements ViewModels.Collection {
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
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()
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
) as MongoDocumentsTab[];
|
||||
let mongoDocumentsTab: MongoDocumentsTab = mongoDocumentsTabs && mongoDocumentsTabs[0];
|
||||
|
||||
@@ -514,7 +511,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: "Documents"
|
||||
tabTitle: "Documents",
|
||||
});
|
||||
this.documentIds([]);
|
||||
|
||||
@@ -531,7 +528,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoDocuments`,
|
||||
isActive: ko.observable(false),
|
||||
onLoadStartKey: startKey,
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||
});
|
||||
this.container.tabsManager.activateNewTab(mongoDocumentsTab);
|
||||
}
|
||||
@@ -546,12 +543,12 @@ export default class Collection implements ViewModels.Collection {
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
|
||||
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
||||
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
|
||||
const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.SettingsV2, tab => {
|
||||
const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.SettingsV2, (tab) => {
|
||||
return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
|
||||
});
|
||||
|
||||
@@ -561,7 +558,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: tabTitle
|
||||
tabTitle: tabTitle,
|
||||
};
|
||||
|
||||
const settingsTabOptions: ViewModels.TabOptions = {
|
||||
@@ -572,7 +569,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
node: this,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/settings`,
|
||||
isActive: ko.observable(false),
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||
};
|
||||
|
||||
let settingsTabV2 = matchingTabs && (matchingTabs[0] as SettingsTabV2);
|
||||
@@ -587,7 +584,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
): void => {
|
||||
const settingsTabV2Options: ViewModels.SettingsTabV2Options = {
|
||||
...settingsTabOptions,
|
||||
getPendingNotification: getPendingNotification
|
||||
getPendingNotification: getPendingNotification,
|
||||
};
|
||||
|
||||
if (!settingsTabV2) {
|
||||
@@ -611,7 +608,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: title
|
||||
tabTitle: title,
|
||||
});
|
||||
|
||||
const queryTab: QueryTab = new QueryTab({
|
||||
@@ -625,7 +622,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
queryText: queryText,
|
||||
partitionKey: collection.partitionKey,
|
||||
onLoadStartKey: startKey,
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(queryTab);
|
||||
@@ -642,7 +639,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: title
|
||||
tabTitle: title,
|
||||
});
|
||||
|
||||
const mongoQueryTab: MongoQueryTab = new MongoQueryTab({
|
||||
@@ -655,7 +652,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
isActive: ko.observable(false),
|
||||
partitionKey: collection.partitionKey,
|
||||
onLoadStartKey: startKey,
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(mongoQueryTab);
|
||||
@@ -671,7 +668,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: title
|
||||
tabTitle: title,
|
||||
});
|
||||
|
||||
const graphTab: GraphTab = new GraphTab({
|
||||
@@ -689,7 +686,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
databaseId: this.databaseId,
|
||||
isTabsContentExpanded: this.container.isTabsContentExpanded,
|
||||
onLoadStartKey: startKey,
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(graphTab);
|
||||
@@ -705,7 +702,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
node: this,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoShell`,
|
||||
isActive: ko.observable(false),
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(mongoShellTab);
|
||||
@@ -767,7 +764,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
this.expandStoredProcedures();
|
||||
}
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -785,10 +782,10 @@ export default class Collection implements ViewModels.Collection {
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
},
|
||||
error => {
|
||||
(error) => {
|
||||
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Failed, {
|
||||
description: "Stored procedures node",
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
@@ -796,7 +793,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
error: getErrorMessage(error)
|
||||
error: getErrorMessage(error),
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -814,7 +811,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -826,7 +823,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
this.expandUserDefinedFunctions();
|
||||
}
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -844,10 +841,10 @@ export default class Collection implements ViewModels.Collection {
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
},
|
||||
error => {
|
||||
(error) => {
|
||||
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Failed, {
|
||||
description: "UDF node",
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
@@ -855,7 +852,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
error: getErrorMessage(error)
|
||||
error: getErrorMessage(error),
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -873,7 +870,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -885,7 +882,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
this.expandTriggers();
|
||||
}
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
(tab) => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -903,10 +900,10 @@ export default class Collection implements ViewModels.Collection {
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
},
|
||||
error => {
|
||||
(error) => {
|
||||
this.isTriggersExpanded(true);
|
||||
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Mark, {
|
||||
description: "Triggers node",
|
||||
@@ -915,7 +912,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
error: getErrorMessage(error)
|
||||
error: getErrorMessage(error),
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -933,36 +930,36 @@ export default class Collection implements ViewModels.Collection {
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
}
|
||||
|
||||
public loadStoredProcedures(): Promise<any> {
|
||||
return readStoredProcedures(this.databaseId, this.id()).then(storedProcedures => {
|
||||
return readStoredProcedures(this.databaseId, this.id()).then((storedProcedures) => {
|
||||
const storedProceduresNodes: ViewModels.TreeNode[] = storedProcedures.map(
|
||||
storedProcedure => new StoredProcedure(this.container, this, storedProcedure)
|
||||
(storedProcedure) => new StoredProcedure(this.container, this, storedProcedure)
|
||||
);
|
||||
const otherNodes = this.children().filter(node => node.nodeKind !== "StoredProcedure");
|
||||
const otherNodes = this.children().filter((node) => node.nodeKind !== "StoredProcedure");
|
||||
const allNodes = otherNodes.concat(storedProceduresNodes);
|
||||
this.children(allNodes);
|
||||
});
|
||||
}
|
||||
|
||||
public loadUserDefinedFunctions(): Promise<any> {
|
||||
return readUserDefinedFunctions(this.databaseId, this.id()).then(userDefinedFunctions => {
|
||||
return readUserDefinedFunctions(this.databaseId, this.id()).then((userDefinedFunctions) => {
|
||||
const userDefinedFunctionsNodes: ViewModels.TreeNode[] = userDefinedFunctions.map(
|
||||
udf => new UserDefinedFunction(this.container, this, udf)
|
||||
(udf) => new UserDefinedFunction(this.container, this, udf)
|
||||
);
|
||||
const otherNodes = this.children().filter(node => node.nodeKind !== "UserDefinedFunction");
|
||||
const otherNodes = this.children().filter((node) => node.nodeKind !== "UserDefinedFunction");
|
||||
const allNodes = otherNodes.concat(userDefinedFunctionsNodes);
|
||||
this.children(allNodes);
|
||||
});
|
||||
}
|
||||
|
||||
public loadTriggers(): Promise<any> {
|
||||
return readTriggers(this.databaseId, this.id()).then(triggers => {
|
||||
const triggerNodes: ViewModels.TreeNode[] = triggers.map(trigger => new Trigger(this.container, this, trigger));
|
||||
const otherNodes = this.children().filter(node => node.nodeKind !== "Trigger");
|
||||
return readTriggers(this.databaseId, this.id()).then((triggers) => {
|
||||
const triggerNodes: ViewModels.TreeNode[] = triggers.map((trigger) => new Trigger(this.container, this, trigger));
|
||||
const otherNodes = this.children().filter((node) => node.nodeKind !== "Trigger");
|
||||
const allNodes = otherNodes.concat(triggerNodes);
|
||||
this.children(allNodes);
|
||||
});
|
||||
@@ -1039,8 +1036,8 @@ export default class Collection implements ViewModels.Collection {
|
||||
endpoint: userContext.endpoint,
|
||||
accessToken: userContext.accessToken,
|
||||
platform: configContext.platform,
|
||||
databaseAccount: userContext.databaseAccount
|
||||
}
|
||||
databaseAccount: userContext.databaseAccount,
|
||||
},
|
||||
};
|
||||
|
||||
documentUploader.postMessage(uploaderMessage);
|
||||
@@ -1072,7 +1069,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (evt: any): void => {
|
||||
const fileData: string = evt.target.result;
|
||||
this._createDocumentsFromFile(file.name, fileData).then(record => {
|
||||
this._createDocumentsFromFile(file.name, fileData).then((record) => {
|
||||
deferred.resolve(record);
|
||||
});
|
||||
};
|
||||
@@ -1082,7 +1079,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
fileName: file.name,
|
||||
numSucceeded: 0,
|
||||
numFailed: 1,
|
||||
errors: [(evt as any).error.message]
|
||||
errors: [(evt as any).error.message],
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1096,7 +1093,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
fileName: fileName,
|
||||
numSucceeded: 0,
|
||||
numFailed: 0,
|
||||
errors: []
|
||||
errors: [],
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -1104,7 +1101,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
|
||||
if (Array.isArray(content)) {
|
||||
await Promise.all(
|
||||
content.map(async documentContent => {
|
||||
content.map(async (documentContent) => {
|
||||
await createDocument(this, documentContent);
|
||||
record.numSucceeded++;
|
||||
})
|
||||
@@ -1153,7 +1150,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
error: getErrorMessage(error),
|
||||
accountName: this.container && this.container.databaseAccount(),
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id()
|
||||
collectionName: this.id(),
|
||||
}),
|
||||
"Settings tree node"
|
||||
);
|
||||
@@ -1247,13 +1244,13 @@ export default class Collection implements ViewModels.Collection {
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience()
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
});
|
||||
|
||||
const params: DataModels.ReadCollectionOfferParams = {
|
||||
collectionId: this.id(),
|
||||
collectionResourceId: this.self,
|
||||
databaseId: this.databaseId
|
||||
databaseId: this.databaseId,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -1266,7 +1263,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience()
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
},
|
||||
startKey
|
||||
);
|
||||
@@ -1279,7 +1276,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
error: getErrorMessage(error),
|
||||
errorStack: getErrorStack(error)
|
||||
errorStack: getErrorStack(error),
|
||||
},
|
||||
startKey
|
||||
);
|
||||
|
||||
@@ -139,7 +139,7 @@ export default class ConflictId {
|
||||
id,
|
||||
partitionKeyValue: partitionKeyValueResolved,
|
||||
partitionKeyProperty: this.partitionKeyProperty,
|
||||
partitionKey: this.partitionKey
|
||||
partitionKey: this.partitionKey,
|
||||
},
|
||||
partitionKeyValueResolved
|
||||
);
|
||||
|
||||
@@ -25,9 +25,9 @@ updateUserContext({
|
||||
documentEndpoint: "fakeEndpoint",
|
||||
tableEndpoint: "fakeEndpoint",
|
||||
gremlinEndpoint: "fakeEndpoint",
|
||||
cassandraEndpoint: "fakeEndpoint"
|
||||
}
|
||||
}
|
||||
cassandraEndpoint: "fakeEndpoint",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe("Add Schema", () => {
|
||||
@@ -70,7 +70,7 @@ describe("Add Schema", () => {
|
||||
resourceGroup: userContext.resourceGroup,
|
||||
accountName: userContext.databaseAccount.name,
|
||||
resource: `dbs/${database.id}/colls/${collection.id}`,
|
||||
status: "new"
|
||||
status: "new",
|
||||
});
|
||||
expect(checkForSchema).not.toBeNull();
|
||||
expect(database.junoClient.getSchema).toBeCalledWith(
|
||||
|
||||
@@ -1,357 +1,357 @@
|
||||
import * as _ from "underscore";
|
||||
import * as ko from "knockout";
|
||||
import Q from "q";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import DatabaseSettingsTab from "../Tabs/DatabaseSettingsTab";
|
||||
import Collection from "./Collection";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
import Explorer from "../Explorer";
|
||||
import { readCollections } from "../../Common/dataAccess/readCollections";
|
||||
import { JunoClient, IJunoResponse } from "../../Juno/JunoClient";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { readDatabaseOffer } from "../../Common/dataAccess/readDatabaseOffer";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
export default class Database implements ViewModels.Database {
|
||||
public nodeKind: string;
|
||||
public container: Explorer;
|
||||
public self: string;
|
||||
public rid: string;
|
||||
public id: ko.Observable<string>;
|
||||
public offer: ko.Observable<DataModels.Offer>;
|
||||
public collections: ko.ObservableArray<Collection>;
|
||||
public isDatabaseExpanded: ko.Observable<boolean>;
|
||||
public isDatabaseShared: ko.Computed<boolean>;
|
||||
public selectedSubnodeKind: ko.Observable<ViewModels.CollectionTabKind>;
|
||||
public junoClient: JunoClient;
|
||||
|
||||
constructor(container: Explorer, data: any) {
|
||||
this.nodeKind = "Database";
|
||||
this.container = container;
|
||||
this.self = data._self;
|
||||
this.rid = data._rid;
|
||||
this.id = ko.observable(data.id);
|
||||
this.offer = ko.observable();
|
||||
this.collections = ko.observableArray<Collection>();
|
||||
this.isDatabaseExpanded = ko.observable<boolean>(false);
|
||||
this.selectedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
|
||||
this.isDatabaseShared = ko.pureComputed(() => {
|
||||
return this.offer && !!this.offer();
|
||||
});
|
||||
this.junoClient = new JunoClient();
|
||||
}
|
||||
|
||||
public onSettingsClick = () => {
|
||||
this.container.selectedNode(this);
|
||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.DatabaseSettings);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Settings node",
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
});
|
||||
|
||||
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
|
||||
const matchingTabs = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.DatabaseSettings,
|
||||
tab => tab.node?.id() === this.id()
|
||||
);
|
||||
let settingsTab: DatabaseSettingsTab = matchingTabs && (matchingTabs[0] as DatabaseSettingsTab);
|
||||
if (!settingsTab) {
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
databaseName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: "Scale"
|
||||
});
|
||||
pendingNotificationsPromise.then(
|
||||
(data: any) => {
|
||||
const pendingNotification: DataModels.Notification = data && data[0];
|
||||
settingsTab = new DatabaseSettingsTab({
|
||||
tabKind: ViewModels.CollectionTabKind.DatabaseSettings,
|
||||
title: "Scale",
|
||||
tabPath: "",
|
||||
node: this,
|
||||
rid: this.rid,
|
||||
database: this,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.databasesWithId(this.id())}/settings`,
|
||||
isActive: ko.observable(false),
|
||||
onLoadStartKey: startKey,
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||
});
|
||||
|
||||
settingsTab.pendingNotification(pendingNotification);
|
||||
this.container.tabsManager.activateNewTab(settingsTab);
|
||||
},
|
||||
(error: any) => {
|
||||
const errorMessage = getErrorMessage(error);
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.Tab,
|
||||
{
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
databaseName: this.id(),
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: "Scale",
|
||||
error: errorMessage,
|
||||
errorStack: getErrorStack(error)
|
||||
},
|
||||
startKey
|
||||
);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while fetching database settings for database ${this.id()}: ${errorMessage}`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
pendingNotificationsPromise.then(
|
||||
(pendingNotification: DataModels.Notification) => {
|
||||
settingsTab.pendingNotification(pendingNotification);
|
||||
this.container.tabsManager.activateTab(settingsTab);
|
||||
},
|
||||
(error: any) => {
|
||||
settingsTab.pendingNotification(undefined);
|
||||
this.container.tabsManager.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 onDeleteDatabaseContextMenuClick(source: ViewModels.Database, event: MouseEvent | KeyboardEvent) {
|
||||
this.container.deleteDatabaseConfirmationPane.open();
|
||||
}
|
||||
|
||||
public selectDatabase() {
|
||||
this.container.selectedNode(this);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Database node",
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
});
|
||||
}
|
||||
|
||||
public async expandDatabase() {
|
||||
if (this.isDatabaseExpanded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.loadOffer();
|
||||
await this.loadCollections();
|
||||
this.isDatabaseExpanded(true);
|
||||
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Mark, {
|
||||
description: "Database node",
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
});
|
||||
}
|
||||
|
||||
public collapseDatabase() {
|
||||
if (!this.isDatabaseExpanded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDatabaseExpanded(false);
|
||||
TelemetryProcessor.trace(Action.CollapseTreeNode, ActionModifiers.Mark, {
|
||||
description: "Database node",
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
});
|
||||
}
|
||||
|
||||
public async loadCollections(): Promise<void> {
|
||||
const collectionVMs: Collection[] = [];
|
||||
const collections: DataModels.Collection[] = await readCollections(this.id());
|
||||
const deltaCollections = this.getDeltaCollections(collections);
|
||||
|
||||
collections.forEach((collection: DataModels.Collection) => {
|
||||
this.addSchema(collection);
|
||||
});
|
||||
|
||||
deltaCollections.toAdd.forEach((collection: DataModels.Collection) => {
|
||||
const collectionVM: Collection = new Collection(this.container, this.id(), collection);
|
||||
collectionVMs.push(collectionVM);
|
||||
});
|
||||
|
||||
//merge collections
|
||||
this.addCollectionsToList(collectionVMs);
|
||||
this.deleteCollectionsFromList(deltaCollections.toDelete);
|
||||
}
|
||||
|
||||
public openAddCollection(database: Database, event: MouseEvent) {
|
||||
database.container.addCollectionPane.databaseId(database.id());
|
||||
database.container.addCollectionPane.open();
|
||||
}
|
||||
|
||||
public findCollectionWithId(collectionId: string): ViewModels.Collection {
|
||||
return _.find(this.collections(), (collection: ViewModels.Collection) => collection.id() === collectionId);
|
||||
}
|
||||
|
||||
public async loadOffer(): Promise<void> {
|
||||
if (!this.container.isServerlessEnabled() && !this.offer()) {
|
||||
const params: DataModels.ReadDatabaseOfferParams = {
|
||||
databaseId: this.id(),
|
||||
databaseResourceId: this.self
|
||||
};
|
||||
this.offer(await readDatabaseOffer(params));
|
||||
}
|
||||
}
|
||||
|
||||
private _getPendingThroughputSplitNotification(): Q.Promise<DataModels.Notification> {
|
||||
if (!this.container) {
|
||||
return Q.resolve(undefined);
|
||||
}
|
||||
|
||||
const deferred: Q.Deferred<DataModels.Notification> = Q.defer<DataModels.Notification>();
|
||||
fetchPortalNotifications().then(
|
||||
notifications => {
|
||||
if (!notifications || notifications.length === 0) {
|
||||
deferred.resolve(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const pendingNotification = _.find(notifications, (notification: DataModels.Notification) => {
|
||||
const throughputUpdateRegExp: RegExp = new RegExp("Throughput update (.*) in progress");
|
||||
return (
|
||||
notification.kind === "message" &&
|
||||
!notification.collectionName &&
|
||||
notification.databaseName === this.id() &&
|
||||
notification.description &&
|
||||
throughputUpdateRegExp.test(notification.description)
|
||||
);
|
||||
});
|
||||
|
||||
deferred.resolve(pendingNotification);
|
||||
},
|
||||
(error: any) => {
|
||||
Logger.logError(
|
||||
JSON.stringify({
|
||||
error: getErrorMessage(error),
|
||||
accountName: this.container && this.container.databaseAccount(),
|
||||
databaseName: this.id(),
|
||||
collectionName: this.id()
|
||||
}),
|
||||
"Settings tree node"
|
||||
);
|
||||
deferred.resolve(undefined);
|
||||
}
|
||||
);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
private getDeltaCollections(
|
||||
updatedCollectionsList: DataModels.Collection[]
|
||||
): { toAdd: DataModels.Collection[]; toDelete: Collection[] } {
|
||||
const collectionsToAdd: DataModels.Collection[] = _.filter(
|
||||
updatedCollectionsList,
|
||||
(collection: DataModels.Collection) => {
|
||||
const collectionExists = _.some(
|
||||
this.collections(),
|
||||
(existingCollection: Collection) => existingCollection.id() === collection.id
|
||||
);
|
||||
return !collectionExists;
|
||||
}
|
||||
);
|
||||
|
||||
let collectionsToDelete: Collection[] = [];
|
||||
ko.utils.arrayForEach(this.collections(), (collection: Collection) => {
|
||||
const collectionPresentInUpdatedList = _.some(
|
||||
updatedCollectionsList,
|
||||
(coll: DataModels.Collection) => coll.id === collection.id()
|
||||
);
|
||||
if (!collectionPresentInUpdatedList) {
|
||||
collectionsToDelete.push(collection);
|
||||
}
|
||||
});
|
||||
|
||||
return { toAdd: collectionsToAdd, toDelete: collectionsToDelete };
|
||||
}
|
||||
|
||||
private addCollectionsToList(collections: Collection[]): void {
|
||||
this.collections(
|
||||
this.collections()
|
||||
.concat(collections)
|
||||
.sort((collection1, collection2) => collection1.id().localeCompare(collection2.id()))
|
||||
);
|
||||
}
|
||||
|
||||
private deleteCollectionsFromList(collectionsToRemove: Collection[]): void {
|
||||
if (collectionsToRemove.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const collectionsToKeep: Collection[] = [];
|
||||
|
||||
ko.utils.arrayForEach(this.collections(), (collection: Collection) => {
|
||||
const shouldRemoveCollection = _.some(collectionsToRemove, (coll: Collection) => coll.id() === collection.id());
|
||||
if (!shouldRemoveCollection) {
|
||||
collectionsToKeep.push(collection);
|
||||
}
|
||||
});
|
||||
|
||||
this.collections(collectionsToKeep);
|
||||
}
|
||||
|
||||
public addSchema(collection: DataModels.Collection, interval?: number): NodeJS.Timeout {
|
||||
let checkForSchema: NodeJS.Timeout = null;
|
||||
interval = interval || 5000;
|
||||
|
||||
if (collection.analyticalStorageTtl !== undefined && this.container.isSchemaEnabled()) {
|
||||
collection.requestSchema = () => {
|
||||
this.junoClient.requestSchema({
|
||||
id: undefined,
|
||||
subscriptionId: userContext.subscriptionId,
|
||||
resourceGroup: userContext.resourceGroup,
|
||||
accountName: userContext.databaseAccount.name,
|
||||
resource: `dbs/${this.id}/colls/${collection.id}`,
|
||||
status: "new"
|
||||
});
|
||||
checkForSchema = setInterval(async () => {
|
||||
const response: IJunoResponse<DataModels.ISchema> = await this.junoClient.getSchema(
|
||||
userContext.databaseAccount.name,
|
||||
this.id(),
|
||||
collection.id
|
||||
);
|
||||
|
||||
if (response.status >= 404) {
|
||||
clearInterval(checkForSchema);
|
||||
}
|
||||
|
||||
if (response.data !== null) {
|
||||
clearInterval(checkForSchema);
|
||||
collection.schema = response.data;
|
||||
}
|
||||
}, interval);
|
||||
};
|
||||
|
||||
collection.requestSchema();
|
||||
}
|
||||
|
||||
return checkForSchema;
|
||||
}
|
||||
}
|
||||
import * as _ from "underscore";
|
||||
import * as ko from "knockout";
|
||||
import Q from "q";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import DatabaseSettingsTab from "../Tabs/DatabaseSettingsTab";
|
||||
import Collection from "./Collection";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
import Explorer from "../Explorer";
|
||||
import { readCollections } from "../../Common/dataAccess/readCollections";
|
||||
import { JunoClient, IJunoResponse } from "../../Juno/JunoClient";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { readDatabaseOffer } from "../../Common/dataAccess/readDatabaseOffer";
|
||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
export default class Database implements ViewModels.Database {
|
||||
public nodeKind: string;
|
||||
public container: Explorer;
|
||||
public self: string;
|
||||
public rid: string;
|
||||
public id: ko.Observable<string>;
|
||||
public offer: ko.Observable<DataModels.Offer>;
|
||||
public collections: ko.ObservableArray<Collection>;
|
||||
public isDatabaseExpanded: ko.Observable<boolean>;
|
||||
public isDatabaseShared: ko.Computed<boolean>;
|
||||
public selectedSubnodeKind: ko.Observable<ViewModels.CollectionTabKind>;
|
||||
public junoClient: JunoClient;
|
||||
|
||||
constructor(container: Explorer, data: any) {
|
||||
this.nodeKind = "Database";
|
||||
this.container = container;
|
||||
this.self = data._self;
|
||||
this.rid = data._rid;
|
||||
this.id = ko.observable(data.id);
|
||||
this.offer = ko.observable();
|
||||
this.collections = ko.observableArray<Collection>();
|
||||
this.isDatabaseExpanded = ko.observable<boolean>(false);
|
||||
this.selectedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
|
||||
this.isDatabaseShared = ko.pureComputed(() => {
|
||||
return this.offer && !!this.offer();
|
||||
});
|
||||
this.junoClient = new JunoClient();
|
||||
}
|
||||
|
||||
public onSettingsClick = () => {
|
||||
this.container.selectedNode(this);
|
||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.DatabaseSettings);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Settings node",
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
|
||||
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
|
||||
const matchingTabs = this.container.tabsManager.getTabs(
|
||||
ViewModels.CollectionTabKind.DatabaseSettings,
|
||||
(tab) => tab.node?.id() === this.id()
|
||||
);
|
||||
let settingsTab: DatabaseSettingsTab = matchingTabs && (matchingTabs[0] as DatabaseSettingsTab);
|
||||
if (!settingsTab) {
|
||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
databaseName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: "Scale",
|
||||
});
|
||||
pendingNotificationsPromise.then(
|
||||
(data: any) => {
|
||||
const pendingNotification: DataModels.Notification = data && data[0];
|
||||
settingsTab = new DatabaseSettingsTab({
|
||||
tabKind: ViewModels.CollectionTabKind.DatabaseSettings,
|
||||
title: "Scale",
|
||||
tabPath: "",
|
||||
node: this,
|
||||
rid: this.rid,
|
||||
database: this,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.databasesWithId(this.id())}/settings`,
|
||||
isActive: ko.observable(false),
|
||||
onLoadStartKey: startKey,
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||
});
|
||||
|
||||
settingsTab.pendingNotification(pendingNotification);
|
||||
this.container.tabsManager.activateNewTab(settingsTab);
|
||||
},
|
||||
(error: any) => {
|
||||
const errorMessage = getErrorMessage(error);
|
||||
TelemetryProcessor.traceFailure(
|
||||
Action.Tab,
|
||||
{
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
databaseName: this.id(),
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: "Scale",
|
||||
error: errorMessage,
|
||||
errorStack: getErrorStack(error),
|
||||
},
|
||||
startKey
|
||||
);
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
`Error while fetching database settings for database ${this.id()}: ${errorMessage}`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
pendingNotificationsPromise.then(
|
||||
(pendingNotification: DataModels.Notification) => {
|
||||
settingsTab.pendingNotification(pendingNotification);
|
||||
this.container.tabsManager.activateTab(settingsTab);
|
||||
},
|
||||
(error: any) => {
|
||||
settingsTab.pendingNotification(undefined);
|
||||
this.container.tabsManager.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 onDeleteDatabaseContextMenuClick(source: ViewModels.Database, event: MouseEvent | KeyboardEvent) {
|
||||
this.container.deleteDatabaseConfirmationPane.open();
|
||||
}
|
||||
|
||||
public selectDatabase() {
|
||||
this.container.selectedNode(this);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Database node",
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
}
|
||||
|
||||
public async expandDatabase() {
|
||||
if (this.isDatabaseExpanded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.loadOffer();
|
||||
await this.loadCollections();
|
||||
this.isDatabaseExpanded(true);
|
||||
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Mark, {
|
||||
description: "Database node",
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
}
|
||||
|
||||
public collapseDatabase() {
|
||||
if (!this.isDatabaseExpanded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDatabaseExpanded(false);
|
||||
TelemetryProcessor.trace(Action.CollapseTreeNode, ActionModifiers.Mark, {
|
||||
description: "Database node",
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
}
|
||||
|
||||
public async loadCollections(): Promise<void> {
|
||||
const collectionVMs: Collection[] = [];
|
||||
const collections: DataModels.Collection[] = await readCollections(this.id());
|
||||
const deltaCollections = this.getDeltaCollections(collections);
|
||||
|
||||
collections.forEach((collection: DataModels.Collection) => {
|
||||
this.addSchema(collection);
|
||||
});
|
||||
|
||||
deltaCollections.toAdd.forEach((collection: DataModels.Collection) => {
|
||||
const collectionVM: Collection = new Collection(this.container, this.id(), collection);
|
||||
collectionVMs.push(collectionVM);
|
||||
});
|
||||
|
||||
//merge collections
|
||||
this.addCollectionsToList(collectionVMs);
|
||||
this.deleteCollectionsFromList(deltaCollections.toDelete);
|
||||
}
|
||||
|
||||
public openAddCollection(database: Database, event: MouseEvent) {
|
||||
database.container.addCollectionPane.databaseId(database.id());
|
||||
database.container.addCollectionPane.open();
|
||||
}
|
||||
|
||||
public findCollectionWithId(collectionId: string): ViewModels.Collection {
|
||||
return _.find(this.collections(), (collection: ViewModels.Collection) => collection.id() === collectionId);
|
||||
}
|
||||
|
||||
public async loadOffer(): Promise<void> {
|
||||
if (!this.container.isServerlessEnabled() && !this.offer()) {
|
||||
const params: DataModels.ReadDatabaseOfferParams = {
|
||||
databaseId: this.id(),
|
||||
databaseResourceId: this.self,
|
||||
};
|
||||
this.offer(await readDatabaseOffer(params));
|
||||
}
|
||||
}
|
||||
|
||||
private _getPendingThroughputSplitNotification(): Q.Promise<DataModels.Notification> {
|
||||
if (!this.container) {
|
||||
return Q.resolve(undefined);
|
||||
}
|
||||
|
||||
const deferred: Q.Deferred<DataModels.Notification> = Q.defer<DataModels.Notification>();
|
||||
fetchPortalNotifications().then(
|
||||
(notifications) => {
|
||||
if (!notifications || notifications.length === 0) {
|
||||
deferred.resolve(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const pendingNotification = _.find(notifications, (notification: DataModels.Notification) => {
|
||||
const throughputUpdateRegExp: RegExp = new RegExp("Throughput update (.*) in progress");
|
||||
return (
|
||||
notification.kind === "message" &&
|
||||
!notification.collectionName &&
|
||||
notification.databaseName === this.id() &&
|
||||
notification.description &&
|
||||
throughputUpdateRegExp.test(notification.description)
|
||||
);
|
||||
});
|
||||
|
||||
deferred.resolve(pendingNotification);
|
||||
},
|
||||
(error: any) => {
|
||||
Logger.logError(
|
||||
JSON.stringify({
|
||||
error: getErrorMessage(error),
|
||||
accountName: this.container && this.container.databaseAccount(),
|
||||
databaseName: this.id(),
|
||||
collectionName: this.id(),
|
||||
}),
|
||||
"Settings tree node"
|
||||
);
|
||||
deferred.resolve(undefined);
|
||||
}
|
||||
);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
private getDeltaCollections(
|
||||
updatedCollectionsList: DataModels.Collection[]
|
||||
): { toAdd: DataModels.Collection[]; toDelete: Collection[] } {
|
||||
const collectionsToAdd: DataModels.Collection[] = _.filter(
|
||||
updatedCollectionsList,
|
||||
(collection: DataModels.Collection) => {
|
||||
const collectionExists = _.some(
|
||||
this.collections(),
|
||||
(existingCollection: Collection) => existingCollection.id() === collection.id
|
||||
);
|
||||
return !collectionExists;
|
||||
}
|
||||
);
|
||||
|
||||
let collectionsToDelete: Collection[] = [];
|
||||
ko.utils.arrayForEach(this.collections(), (collection: Collection) => {
|
||||
const collectionPresentInUpdatedList = _.some(
|
||||
updatedCollectionsList,
|
||||
(coll: DataModels.Collection) => coll.id === collection.id()
|
||||
);
|
||||
if (!collectionPresentInUpdatedList) {
|
||||
collectionsToDelete.push(collection);
|
||||
}
|
||||
});
|
||||
|
||||
return { toAdd: collectionsToAdd, toDelete: collectionsToDelete };
|
||||
}
|
||||
|
||||
private addCollectionsToList(collections: Collection[]): void {
|
||||
this.collections(
|
||||
this.collections()
|
||||
.concat(collections)
|
||||
.sort((collection1, collection2) => collection1.id().localeCompare(collection2.id()))
|
||||
);
|
||||
}
|
||||
|
||||
private deleteCollectionsFromList(collectionsToRemove: Collection[]): void {
|
||||
if (collectionsToRemove.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const collectionsToKeep: Collection[] = [];
|
||||
|
||||
ko.utils.arrayForEach(this.collections(), (collection: Collection) => {
|
||||
const shouldRemoveCollection = _.some(collectionsToRemove, (coll: Collection) => coll.id() === collection.id());
|
||||
if (!shouldRemoveCollection) {
|
||||
collectionsToKeep.push(collection);
|
||||
}
|
||||
});
|
||||
|
||||
this.collections(collectionsToKeep);
|
||||
}
|
||||
|
||||
public addSchema(collection: DataModels.Collection, interval?: number): NodeJS.Timeout {
|
||||
let checkForSchema: NodeJS.Timeout = null;
|
||||
interval = interval || 5000;
|
||||
|
||||
if (collection.analyticalStorageTtl !== undefined && this.container.isSchemaEnabled()) {
|
||||
collection.requestSchema = () => {
|
||||
this.junoClient.requestSchema({
|
||||
id: undefined,
|
||||
subscriptionId: userContext.subscriptionId,
|
||||
resourceGroup: userContext.resourceGroup,
|
||||
accountName: userContext.databaseAccount.name,
|
||||
resource: `dbs/${this.id}/colls/${collection.id}`,
|
||||
status: "new",
|
||||
});
|
||||
checkForSchema = setInterval(async () => {
|
||||
const response: IJunoResponse<DataModels.ISchema> = await this.junoClient.getSchema(
|
||||
userContext.databaseAccount.name,
|
||||
this.id(),
|
||||
collection.id
|
||||
);
|
||||
|
||||
if (response.status >= 404) {
|
||||
clearInterval(checkForSchema);
|
||||
}
|
||||
|
||||
if (response.data !== null) {
|
||||
clearInterval(checkForSchema);
|
||||
collection.schema = response.data;
|
||||
}
|
||||
}, interval);
|
||||
};
|
||||
|
||||
collection.requestSchema();
|
||||
}
|
||||
|
||||
return checkForSchema;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,71 +1,71 @@
|
||||
import * as ko from "knockout";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import DocumentsTab from "../Tabs/DocumentsTab";
|
||||
|
||||
export default class DocumentId {
|
||||
public container: DocumentsTab;
|
||||
public rid: string;
|
||||
public self: string;
|
||||
public ts: string;
|
||||
public id: ko.Observable<string>;
|
||||
public partitionKeyProperty: string;
|
||||
public partitionKey: DataModels.PartitionKey;
|
||||
public partitionKeyValue: any;
|
||||
public stringPartitionKeyValue: string;
|
||||
public isDirty: ko.Observable<boolean>;
|
||||
|
||||
constructor(container: DocumentsTab, data: any, partitionKeyValue: any) {
|
||||
this.container = container;
|
||||
this.self = data._self;
|
||||
this.rid = data._rid;
|
||||
this.ts = data._ts;
|
||||
this.partitionKeyValue = partitionKeyValue;
|
||||
this.partitionKeyProperty = container && container.partitionKeyProperty;
|
||||
this.partitionKey = container && container.partitionKey;
|
||||
this.stringPartitionKeyValue = this.getPartitionKeyValueAsString();
|
||||
this.id = ko.observable(data.id);
|
||||
this.isDirty = ko.observable(false);
|
||||
}
|
||||
|
||||
public click() {
|
||||
if (!this.container.isEditorDirty() || window.confirm("Your unsaved changes will be lost.")) {
|
||||
this.loadDocument();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
public partitionKeyHeader(): Object {
|
||||
if (!this.partitionKeyProperty) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (this.partitionKeyValue === undefined) {
|
||||
return [{}];
|
||||
}
|
||||
|
||||
return [this.partitionKeyValue];
|
||||
}
|
||||
|
||||
public getPartitionKeyValueAsString(): string {
|
||||
const partitionKeyValue: any = this.partitionKeyValue;
|
||||
const typeOfPartitionKeyValue: string = typeof partitionKeyValue;
|
||||
|
||||
if (
|
||||
typeOfPartitionKeyValue === "undefined" ||
|
||||
typeOfPartitionKeyValue === "null" ||
|
||||
typeOfPartitionKeyValue === "object"
|
||||
) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (typeOfPartitionKeyValue === "string") {
|
||||
return partitionKeyValue;
|
||||
}
|
||||
|
||||
return JSON.stringify(partitionKeyValue);
|
||||
}
|
||||
|
||||
public async loadDocument(): Promise<void> {
|
||||
await this.container.selectDocument(this);
|
||||
}
|
||||
}
|
||||
import * as ko from "knockout";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import DocumentsTab from "../Tabs/DocumentsTab";
|
||||
|
||||
export default class DocumentId {
|
||||
public container: DocumentsTab;
|
||||
public rid: string;
|
||||
public self: string;
|
||||
public ts: string;
|
||||
public id: ko.Observable<string>;
|
||||
public partitionKeyProperty: string;
|
||||
public partitionKey: DataModels.PartitionKey;
|
||||
public partitionKeyValue: any;
|
||||
public stringPartitionKeyValue: string;
|
||||
public isDirty: ko.Observable<boolean>;
|
||||
|
||||
constructor(container: DocumentsTab, data: any, partitionKeyValue: any) {
|
||||
this.container = container;
|
||||
this.self = data._self;
|
||||
this.rid = data._rid;
|
||||
this.ts = data._ts;
|
||||
this.partitionKeyValue = partitionKeyValue;
|
||||
this.partitionKeyProperty = container && container.partitionKeyProperty;
|
||||
this.partitionKey = container && container.partitionKey;
|
||||
this.stringPartitionKeyValue = this.getPartitionKeyValueAsString();
|
||||
this.id = ko.observable(data.id);
|
||||
this.isDirty = ko.observable(false);
|
||||
}
|
||||
|
||||
public click() {
|
||||
if (!this.container.isEditorDirty() || window.confirm("Your unsaved changes will be lost.")) {
|
||||
this.loadDocument();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
public partitionKeyHeader(): Object {
|
||||
if (!this.partitionKeyProperty) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (this.partitionKeyValue === undefined) {
|
||||
return [{}];
|
||||
}
|
||||
|
||||
return [this.partitionKeyValue];
|
||||
}
|
||||
|
||||
public getPartitionKeyValueAsString(): string {
|
||||
const partitionKeyValue: any = this.partitionKeyValue;
|
||||
const typeOfPartitionKeyValue: string = typeof partitionKeyValue;
|
||||
|
||||
if (
|
||||
typeOfPartitionKeyValue === "undefined" ||
|
||||
typeOfPartitionKeyValue === "null" ||
|
||||
typeOfPartitionKeyValue === "object"
|
||||
) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (typeOfPartitionKeyValue === "string") {
|
||||
return partitionKeyValue;
|
||||
}
|
||||
|
||||
return JSON.stringify(partitionKeyValue);
|
||||
}
|
||||
|
||||
public async loadDocument(): Promise<void> {
|
||||
await this.container.selectDocument(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import * as ko from "knockout";
|
||||
import DocumentId from "./DocumentId";
|
||||
import DocumentsTab from "../Tabs/DocumentsTab";
|
||||
|
||||
export default class ObjectId extends DocumentId {
|
||||
constructor(container: DocumentsTab, data: any, partitionKeyValue: any) {
|
||||
super(container, data, partitionKeyValue);
|
||||
if (typeof data._id === "object") {
|
||||
this.id = ko.observable(data._id[Object.keys(data._id)[0]]);
|
||||
} else {
|
||||
this.id = ko.observable(data._id);
|
||||
}
|
||||
}
|
||||
}
|
||||
import * as ko from "knockout";
|
||||
import DocumentId from "./DocumentId";
|
||||
import DocumentsTab from "../Tabs/DocumentsTab";
|
||||
|
||||
export default class ObjectId extends DocumentId {
|
||||
constructor(container: DocumentsTab, data: any, partitionKeyValue: any) {
|
||||
super(container, data, partitionKeyValue);
|
||||
if (typeof data._id === "object") {
|
||||
this.id = ko.observable(data._id[Object.keys(data._id)[0]]);
|
||||
} else {
|
||||
this.id = ko.observable(data._id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
|
||||
return Q.resolve();
|
||||
@@ -71,7 +71,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: title
|
||||
tabTitle: title,
|
||||
});
|
||||
|
||||
const queryTab: QueryTab = new QueryTab({
|
||||
@@ -100,7 +100,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
||||
partitionKey: collection.partitionKey,
|
||||
resourceTokenPartitionKey: this.container.resourceTokenPartitionKey(),
|
||||
onLoadStartKey: startKey,
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(queryTab);
|
||||
@@ -115,7 +115,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
||||
databaseName: this.databaseId,
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
|
||||
const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
|
||||
@@ -135,7 +135,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
||||
collectionName: this.id(),
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: "Items"
|
||||
tabTitle: "Items",
|
||||
});
|
||||
|
||||
documentsTab = new DocumentsTab({
|
||||
@@ -150,7 +150,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
||||
tabPath: `${this.databaseId}>${this.id()}>Documents`,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/documents`,
|
||||
onLoadStartKey: startKey,
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(documentsTab);
|
||||
|
||||
@@ -10,15 +10,15 @@ describe("ResourceTreeAdapter", () => {
|
||||
selectedNode: ko.observable<ViewModels.TreeNode>({
|
||||
nodeKind: "nodeKind",
|
||||
rid: "rid",
|
||||
id: ko.observable<string>("id")
|
||||
id: ko.observable<string>("id"),
|
||||
}),
|
||||
tabsManager: {
|
||||
activeTab: ko.observable<TabsBase>({
|
||||
tabKind: ViewModels.CollectionTabKind.Documents
|
||||
} as TabsBase)
|
||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||
} as TabsBase),
|
||||
},
|
||||
isNotebookEnabled: ko.observable<boolean>(true),
|
||||
nonSystemDatabases: ko.observable<ViewModels.Database[]>([])
|
||||
nonSystemDatabases: ko.observable<ViewModels.Database[]>([]),
|
||||
} as unknown) as Explorer);
|
||||
|
||||
// TODO isDataNodeSelected needs a better design and refactor, but for now, we protect some of the code paths
|
||||
@@ -52,11 +52,11 @@ describe("ResourceTreeAdapter", () => {
|
||||
nodeKind: "Database",
|
||||
rid: "dbrid",
|
||||
id: ko.observable<string>("dbid"),
|
||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind)
|
||||
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
|
||||
ViewModels.CollectionTabKind.Documents,
|
||||
]);
|
||||
expect(isDataNodeSelected).toBeTruthy();
|
||||
});
|
||||
@@ -65,14 +65,14 @@ describe("ResourceTreeAdapter", () => {
|
||||
let subNodeKind = ViewModels.CollectionTabKind.Documents;
|
||||
const explorer = mockContainer();
|
||||
explorer.tabsManager.activeTab({
|
||||
tabKind: subNodeKind
|
||||
tabKind: subNodeKind,
|
||||
} as TabsBase);
|
||||
explorer.selectedNode(({
|
||||
nodeKind: "Collection",
|
||||
rid: "collrid",
|
||||
databaseId: "dbid",
|
||||
id: ko.observable<string>("collid"),
|
||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind)
|
||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind),
|
||||
} as unknown) as ViewModels.TreeNode);
|
||||
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
||||
let isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbid", "collid", [subNodeKind]);
|
||||
@@ -80,14 +80,14 @@ describe("ResourceTreeAdapter", () => {
|
||||
|
||||
subNodeKind = ViewModels.CollectionTabKind.Graph;
|
||||
explorer.tabsManager.activeTab({
|
||||
tabKind: subNodeKind
|
||||
tabKind: subNodeKind,
|
||||
} as TabsBase);
|
||||
explorer.selectedNode(({
|
||||
nodeKind: "Collection",
|
||||
rid: "collrid",
|
||||
databaseId: "dbid",
|
||||
id: ko.observable<string>("collid"),
|
||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind)
|
||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(subNodeKind),
|
||||
} as unknown) as ViewModels.TreeNode);
|
||||
isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbid", "collid", [subNodeKind]);
|
||||
expect(isDataNodeSelected).toBeTruthy();
|
||||
@@ -100,14 +100,14 @@ describe("ResourceTreeAdapter", () => {
|
||||
rid: "collrid",
|
||||
databaseId: "dbid",
|
||||
id: ko.observable<string>("collid"),
|
||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(ViewModels.CollectionTabKind.Documents)
|
||||
selectedSubnodeKind: ko.observable<ViewModels.CollectionTabKind>(ViewModels.CollectionTabKind.Documents),
|
||||
} as unknown) as ViewModels.TreeNode);
|
||||
explorer.tabsManager.activeTab({
|
||||
tabKind: ViewModels.CollectionTabKind.Documents
|
||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||
} as TabsBase);
|
||||
const resourceTreeAdapter = new ResourceTreeAdapter(explorer);
|
||||
const isDataNodeSelected = resourceTreeAdapter.isDataNodeSelected("dbid", "collid", [
|
||||
ViewModels.CollectionTabKind.Settings
|
||||
ViewModels.CollectionTabKind.Settings,
|
||||
]);
|
||||
expect(isDataNodeSelected).toBeFalsy();
|
||||
});
|
||||
|
||||
@@ -16,196 +16,196 @@ const schema: DataModels.ISchema = {
|
||||
{
|
||||
dataType: {
|
||||
code: 15,
|
||||
name: "String"
|
||||
name: "String",
|
||||
},
|
||||
hasNulls: true,
|
||||
isArray: false,
|
||||
schemaType: {
|
||||
code: 0,
|
||||
name: "Data"
|
||||
name: "Data",
|
||||
},
|
||||
name: "_rid",
|
||||
path: "_rid",
|
||||
maxRepetitionLevel: 0,
|
||||
maxDefinitionLevel: 1
|
||||
maxDefinitionLevel: 1,
|
||||
},
|
||||
{
|
||||
dataType: {
|
||||
code: 11,
|
||||
name: "Int64"
|
||||
name: "Int64",
|
||||
},
|
||||
hasNulls: true,
|
||||
isArray: false,
|
||||
schemaType: {
|
||||
code: 0,
|
||||
name: "Data"
|
||||
name: "Data",
|
||||
},
|
||||
name: "_ts",
|
||||
path: "_ts",
|
||||
maxRepetitionLevel: 0,
|
||||
maxDefinitionLevel: 1
|
||||
maxDefinitionLevel: 1,
|
||||
},
|
||||
{
|
||||
dataType: {
|
||||
code: 15,
|
||||
name: "String"
|
||||
name: "String",
|
||||
},
|
||||
hasNulls: true,
|
||||
isArray: false,
|
||||
schemaType: {
|
||||
code: 0,
|
||||
name: "Data"
|
||||
name: "Data",
|
||||
},
|
||||
name: "id",
|
||||
path: "id",
|
||||
maxRepetitionLevel: 0,
|
||||
maxDefinitionLevel: 1
|
||||
maxDefinitionLevel: 1,
|
||||
},
|
||||
{
|
||||
dataType: {
|
||||
code: 15,
|
||||
name: "String"
|
||||
name: "String",
|
||||
},
|
||||
hasNulls: true,
|
||||
isArray: false,
|
||||
schemaType: {
|
||||
code: 0,
|
||||
name: "Data"
|
||||
name: "Data",
|
||||
},
|
||||
name: "pk",
|
||||
path: "pk",
|
||||
maxRepetitionLevel: 0,
|
||||
maxDefinitionLevel: 1
|
||||
maxDefinitionLevel: 1,
|
||||
},
|
||||
{
|
||||
dataType: {
|
||||
code: 15,
|
||||
name: "String"
|
||||
name: "String",
|
||||
},
|
||||
hasNulls: true,
|
||||
isArray: false,
|
||||
schemaType: {
|
||||
code: 0,
|
||||
name: "Data"
|
||||
name: "Data",
|
||||
},
|
||||
name: "other",
|
||||
path: "other",
|
||||
maxRepetitionLevel: 0,
|
||||
maxDefinitionLevel: 1
|
||||
maxDefinitionLevel: 1,
|
||||
},
|
||||
{
|
||||
dataType: {
|
||||
code: 15,
|
||||
name: "String"
|
||||
name: "String",
|
||||
},
|
||||
hasNulls: true,
|
||||
isArray: false,
|
||||
schemaType: {
|
||||
code: 0,
|
||||
name: "Data"
|
||||
name: "Data",
|
||||
},
|
||||
name: "name",
|
||||
path: "nested.name",
|
||||
maxRepetitionLevel: 0,
|
||||
maxDefinitionLevel: 1
|
||||
maxDefinitionLevel: 1,
|
||||
},
|
||||
{
|
||||
dataType: {
|
||||
code: 11,
|
||||
name: "Int64"
|
||||
name: "Int64",
|
||||
},
|
||||
hasNulls: true,
|
||||
isArray: false,
|
||||
schemaType: {
|
||||
code: 0,
|
||||
name: "Data"
|
||||
name: "Data",
|
||||
},
|
||||
name: "someNumber",
|
||||
path: "nested.someNumber",
|
||||
maxRepetitionLevel: 0,
|
||||
maxDefinitionLevel: 1
|
||||
maxDefinitionLevel: 1,
|
||||
},
|
||||
{
|
||||
dataType: {
|
||||
code: 17,
|
||||
name: "Double"
|
||||
name: "Double",
|
||||
},
|
||||
hasNulls: true,
|
||||
isArray: false,
|
||||
schemaType: {
|
||||
code: 0,
|
||||
name: "Data"
|
||||
name: "Data",
|
||||
},
|
||||
name: "anotherNumber",
|
||||
path: "nested.anotherNumber",
|
||||
maxRepetitionLevel: 0,
|
||||
maxDefinitionLevel: 1
|
||||
maxDefinitionLevel: 1,
|
||||
},
|
||||
{
|
||||
dataType: {
|
||||
code: 15,
|
||||
name: "String"
|
||||
name: "String",
|
||||
},
|
||||
hasNulls: true,
|
||||
isArray: false,
|
||||
schemaType: {
|
||||
code: 0,
|
||||
name: "Data"
|
||||
name: "Data",
|
||||
},
|
||||
name: "name",
|
||||
path: "items.list.items.name",
|
||||
maxRepetitionLevel: 1,
|
||||
maxDefinitionLevel: 3
|
||||
maxDefinitionLevel: 3,
|
||||
},
|
||||
{
|
||||
dataType: {
|
||||
code: 11,
|
||||
name: "Int64"
|
||||
name: "Int64",
|
||||
},
|
||||
hasNulls: true,
|
||||
isArray: false,
|
||||
schemaType: {
|
||||
code: 0,
|
||||
name: "Data"
|
||||
name: "Data",
|
||||
},
|
||||
name: "someNumber",
|
||||
path: "items.list.items.someNumber",
|
||||
maxRepetitionLevel: 1,
|
||||
maxDefinitionLevel: 3
|
||||
maxDefinitionLevel: 3,
|
||||
},
|
||||
{
|
||||
dataType: {
|
||||
code: 17,
|
||||
name: "Double"
|
||||
name: "Double",
|
||||
},
|
||||
hasNulls: true,
|
||||
isArray: false,
|
||||
schemaType: {
|
||||
code: 0,
|
||||
name: "Data"
|
||||
name: "Data",
|
||||
},
|
||||
name: "anotherNumber",
|
||||
path: "items.list.items.anotherNumber",
|
||||
maxRepetitionLevel: 1,
|
||||
maxDefinitionLevel: 3
|
||||
maxDefinitionLevel: 3,
|
||||
},
|
||||
{
|
||||
dataType: {
|
||||
code: 15,
|
||||
name: "String"
|
||||
name: "String",
|
||||
},
|
||||
hasNulls: true,
|
||||
isArray: false,
|
||||
schemaType: {
|
||||
code: 0,
|
||||
name: "Data"
|
||||
name: "Data",
|
||||
},
|
||||
name: "_etag",
|
||||
path: "_etag",
|
||||
maxRepetitionLevel: 0,
|
||||
maxDefinitionLevel: 1
|
||||
}
|
||||
]
|
||||
maxDefinitionLevel: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const createMockContainer = (): Explorer => {
|
||||
@@ -243,7 +243,7 @@ describe("Resource tree for schema", () => {
|
||||
const rootNode: TreeNode = resourceTree.buildSchemaNode(createMockCollection());
|
||||
const props: TreeComponentProps = {
|
||||
rootNode,
|
||||
className: "dataResourceTree"
|
||||
className: "dataResourceTree",
|
||||
};
|
||||
const wrapper = shallow(<TreeComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
@@ -57,7 +57,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
|
||||
this.container.selectedNode.subscribe((newValue: any) => this.triggerRender());
|
||||
this.container.tabsManager.activeTab.subscribe((newValue: TabsBase) => this.triggerRender());
|
||||
this.container.isNotebookEnabled.subscribe(newValue => this.triggerRender());
|
||||
this.container.isNotebookEnabled.subscribe((newValue) => this.triggerRender());
|
||||
|
||||
this.koSubsDatabaseIdMap = new ArrayHashMap();
|
||||
this.koSubsCollectionIdMap = new ArrayHashMap();
|
||||
@@ -80,7 +80,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
if (myNotebooksTree.children) {
|
||||
// Count 1st generation children (tree is lazy-loaded)
|
||||
const nodeCounts = { files: 0, notebooks: 0, directories: 0 };
|
||||
myNotebooksTree.children.forEach(treeNode => {
|
||||
myNotebooksTree.children.forEach((treeNode) => {
|
||||
switch ((treeNode as NotebookContentItem).type) {
|
||||
case NotebookContentItemType.File:
|
||||
nodeCounts.files++;
|
||||
@@ -129,13 +129,13 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
this.galleryContentRoot = {
|
||||
name: "Gallery",
|
||||
path: "Gallery",
|
||||
type: NotebookContentItemType.File
|
||||
type: NotebookContentItemType.File,
|
||||
};
|
||||
|
||||
this.myNotebooksContentRoot = {
|
||||
name: ResourceTreeAdapter.MyNotebooksTitle,
|
||||
path: this.container.getNotebookBasePath(),
|
||||
type: NotebookContentItemType.Directory
|
||||
type: NotebookContentItemType.Directory,
|
||||
};
|
||||
|
||||
// Only if notebook server is available we can refresh
|
||||
@@ -152,7 +152,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
this.gitHubNotebooksContentRoot = {
|
||||
name: ResourceTreeAdapter.GitHubReposTitle,
|
||||
path: ResourceTreeAdapter.PseudoDirPath,
|
||||
type: NotebookContentItemType.Directory
|
||||
type: NotebookContentItemType.Directory,
|
||||
};
|
||||
} else {
|
||||
this.gitHubNotebooksContentRoot = undefined;
|
||||
@@ -164,20 +164,20 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
public initializeGitHubRepos(pinnedRepos: IPinnedRepo[]): void {
|
||||
if (this.gitHubNotebooksContentRoot) {
|
||||
this.gitHubNotebooksContentRoot.children = [];
|
||||
pinnedRepos?.forEach(pinnedRepo => {
|
||||
pinnedRepos?.forEach((pinnedRepo) => {
|
||||
const repoFullName = GitHubUtils.toRepoFullName(pinnedRepo.owner, pinnedRepo.name);
|
||||
const repoTreeItem: NotebookContentItem = {
|
||||
name: repoFullName,
|
||||
path: ResourceTreeAdapter.PseudoDirPath,
|
||||
type: NotebookContentItemType.Directory,
|
||||
children: []
|
||||
children: [],
|
||||
};
|
||||
|
||||
pinnedRepo.branches.forEach(branch => {
|
||||
pinnedRepo.branches.forEach((branch) => {
|
||||
repoTreeItem.children.push({
|
||||
name: branch.name,
|
||||
path: GitHubUtils.toContentUri(pinnedRepo.owner, pinnedRepo.name, branch.name, ""),
|
||||
type: NotebookContentItemType.Directory
|
||||
type: NotebookContentItemType.Directory,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -198,7 +198,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
children: [],
|
||||
isSelected: () => this.isDataNodeSelected(database.id()),
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createDatabaseContextMenu(this.container),
|
||||
onClick: async isExpanded => {
|
||||
onClick: async (isExpanded) => {
|
||||
// Rewritten version of expandCollapseDatabase():
|
||||
if (isExpanded) {
|
||||
database.collapseDatabase();
|
||||
@@ -213,7 +213,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
this.container.onUpdateTabsButtons([]);
|
||||
this.container.tabsManager.refreshActiveTab((tab: TabsBase) => tab.collection?.databaseId === database.id());
|
||||
},
|
||||
onContextMenuOpen: () => this.container.selectedNode(database)
|
||||
onContextMenuOpen: () => this.container.selectedNode(database),
|
||||
};
|
||||
|
||||
if (database.isDatabaseShared()) {
|
||||
@@ -221,7 +221,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
label: "Scale",
|
||||
isSelected: () =>
|
||||
this.isDataNodeSelected(database.id(), undefined, [ViewModels.CollectionTabKind.DatabaseSettings]),
|
||||
onClick: database.onSettingsClick.bind(database)
|
||||
onClick: database.onSettingsClick.bind(database),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
return {
|
||||
label: undefined,
|
||||
isExpanded: true,
|
||||
children: databaseTreeNodes
|
||||
children: databaseTreeNodes,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -269,16 +269,16 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
description: "Data",
|
||||
data: {
|
||||
databaseId: collection.databaseId,
|
||||
collectionId: collection.id()
|
||||
}
|
||||
collectionId: collection.id(),
|
||||
},
|
||||
});
|
||||
},
|
||||
isSelected: () =>
|
||||
this.isDataNodeSelected(collection.databaseId, collection.id(), [
|
||||
ViewModels.CollectionTabKind.Documents,
|
||||
ViewModels.CollectionTabKind.Graph
|
||||
ViewModels.CollectionTabKind.Graph,
|
||||
]),
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(this.container, collection)
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(this.container, collection),
|
||||
});
|
||||
|
||||
if (!this.container.isPreferredApiCassandra() || !this.container.isServerlessEnabled()) {
|
||||
@@ -286,7 +286,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
label: database.isDatabaseShared() || this.container.isServerlessEnabled() ? "Settings" : "Scale & Settings",
|
||||
onClick: collection.onSettingsClick.bind(collection),
|
||||
isSelected: () =>
|
||||
this.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Settings])
|
||||
this.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Settings]),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
label: "Conflicts",
|
||||
onClick: collection.onConflictsClick.bind(collection),
|
||||
isSelected: () =>
|
||||
this.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Conflicts])
|
||||
this.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Conflicts]),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -343,7 +343,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
}
|
||||
},
|
||||
isSelected: () => this.isDataNodeSelected(collection.databaseId, collection.id()),
|
||||
onContextMenuOpen: () => this.container.selectedNode(collection)
|
||||
onContextMenuOpen: () => this.container.selectedNode(collection),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -355,9 +355,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
onClick: sp.open.bind(sp),
|
||||
isSelected: () =>
|
||||
this.isDataNodeSelected(collection.databaseId, collection.id(), [
|
||||
ViewModels.CollectionTabKind.StoredProcedures
|
||||
ViewModels.CollectionTabKind.StoredProcedures,
|
||||
]),
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createStoreProcedureContextMenuItems(this.container, sp)
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createStoreProcedureContextMenuItems(this.container, sp),
|
||||
})),
|
||||
onClick: () => {
|
||||
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.StoredProcedures);
|
||||
@@ -365,7 +365,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -377,9 +377,12 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
onClick: udf.open.bind(udf),
|
||||
isSelected: () =>
|
||||
this.isDataNodeSelected(collection.databaseId, collection.id(), [
|
||||
ViewModels.CollectionTabKind.UserDefinedFunctions
|
||||
ViewModels.CollectionTabKind.UserDefinedFunctions,
|
||||
]),
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createUserDefinedFunctionContextMenuItems(this.container, udf)
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createUserDefinedFunctionContextMenuItems(
|
||||
this.container,
|
||||
udf
|
||||
),
|
||||
})),
|
||||
onClick: () => {
|
||||
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.UserDefinedFunctions);
|
||||
@@ -387,7 +390,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -399,7 +402,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
onClick: trigger.open.bind(trigger),
|
||||
isSelected: () =>
|
||||
this.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Triggers]),
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createTriggerContextMenuItems(this.container, trigger)
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createTriggerContextMenuItems(this.container, trigger),
|
||||
})),
|
||||
onClick: () => {
|
||||
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Triggers);
|
||||
@@ -407,7 +410,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
(tab: TabsBase) =>
|
||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -428,7 +431,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
(tab: TabsBase) => tab.collection && tab.collection.rid === collection.rid
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -484,7 +487,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
let notebooksTree: TreeNode = {
|
||||
label: undefined,
|
||||
isExpanded: true,
|
||||
children: []
|
||||
children: [],
|
||||
};
|
||||
|
||||
if (this.galleryContentRoot) {
|
||||
@@ -497,7 +500,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
|
||||
if (this.gitHubNotebooksContentRoot) {
|
||||
// collapse all other notebook nodes
|
||||
notebooksTree.children.forEach(node => (node.isExpanded = false));
|
||||
notebooksTree.children.forEach((node) => (node.isExpanded = false));
|
||||
notebooksTree.children.push(this.buildGitHubNotebooksTree());
|
||||
}
|
||||
|
||||
@@ -523,7 +526,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
LocalStorageUtility.setEntryBoolean(StorageKey.GalleryCalloutDismissed, true);
|
||||
this.triggerRender();
|
||||
},
|
||||
setInitialFocus: true
|
||||
setInitialFocus: true,
|
||||
};
|
||||
|
||||
const openGalleryProps: ILinkProps = {
|
||||
@@ -531,7 +534,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
LocalStorageUtility.setEntryBoolean(StorageKey.GalleryCalloutDismissed, true);
|
||||
this.container.openGallery();
|
||||
this.triggerRender();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -559,7 +562,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
isSelected: () => {
|
||||
const activeTab = this.container.tabsManager.activeTab();
|
||||
return activeTab && activeTab.tabKind === ViewModels.CollectionTabKind.Gallery;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -567,7 +570,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
const myNotebooksTree: TreeNode = this.buildNotebookDirectoryNode(
|
||||
this.myNotebooksContentRoot,
|
||||
(item: NotebookContentItem) => {
|
||||
this.container.openNotebook(item).then(hasOpened => {
|
||||
this.container.openNotebook(item).then((hasOpened) => {
|
||||
if (hasOpened) {
|
||||
this.pushItemToMostRecent(item);
|
||||
}
|
||||
@@ -580,7 +583,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
myNotebooksTree.isExpanded = true;
|
||||
myNotebooksTree.isAlphaSorted = true;
|
||||
// Remove "Delete" menu item from context menu
|
||||
myNotebooksTree.contextMenu = myNotebooksTree.contextMenu.filter(menuItem => menuItem.label !== "Delete");
|
||||
myNotebooksTree.contextMenu = myNotebooksTree.contextMenu.filter((menuItem) => menuItem.label !== "Delete");
|
||||
return myNotebooksTree;
|
||||
}
|
||||
|
||||
@@ -588,7 +591,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
const gitHubNotebooksTree: TreeNode = this.buildNotebookDirectoryNode(
|
||||
this.gitHubNotebooksContentRoot,
|
||||
(item: NotebookContentItem) => {
|
||||
this.container.openNotebook(item).then(hasOpened => {
|
||||
this.container.openNotebook(item).then((hasOpened) => {
|
||||
if (hasOpened) {
|
||||
this.pushItemToMostRecent(item);
|
||||
}
|
||||
@@ -601,7 +604,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
gitHubNotebooksTree.contextMenu = [
|
||||
{
|
||||
label: "Manage GitHub settings",
|
||||
onClick: () => this.container.gitHubReposPane.open()
|
||||
onClick: () => this.container.gitHubReposPane.open(),
|
||||
},
|
||||
{
|
||||
label: "Disconnect from GitHub",
|
||||
@@ -609,11 +612,11 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
TelemetryProcessor.trace(Action.NotebooksGitHubDisconnect, ActionModifiers.Mark, {
|
||||
databaseAccountName: this.container.databaseAccount() && this.container.databaseAccount().name,
|
||||
defaultExperience: this.container.defaultExperience && this.container.defaultExperience(),
|
||||
dataExplorerArea: Areas.Notebook
|
||||
dataExplorerArea: Areas.Notebook,
|
||||
});
|
||||
this.container.notebookManager?.gitHubOAuthService.logout();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
gitHubNotebooksTree.isExpanded = true;
|
||||
@@ -629,8 +632,8 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
description: "Notebook",
|
||||
data: {
|
||||
name: item.name,
|
||||
path: item.path
|
||||
}
|
||||
path: item.path,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -643,7 +646,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
if (!item || !item.children) {
|
||||
return [];
|
||||
} else {
|
||||
return item.children.map(item => {
|
||||
return item.children.map((item) => {
|
||||
const result =
|
||||
item.type === NotebookContentItemType.Directory
|
||||
? this.buildNotebookDirectoryNode(item, onFileClick, createDirectoryContextMenu, createFileContextMenu)
|
||||
@@ -676,7 +679,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
);
|
||||
},
|
||||
contextMenu: createFileContextMenu && this.createFileContextMenu(item),
|
||||
data: item
|
||||
data: item,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -685,7 +688,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
{
|
||||
label: "Rename",
|
||||
iconSrc: NotebookIcon,
|
||||
onClick: () => this.container.renameNotebook(item)
|
||||
onClick: () => this.container.renameNotebook(item),
|
||||
},
|
||||
{
|
||||
label: "Delete",
|
||||
@@ -699,23 +702,23 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
"Cancel",
|
||||
undefined
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Copy to ...",
|
||||
iconSrc: CopyIcon,
|
||||
onClick: () => this.copyNotebook(item)
|
||||
onClick: () => this.copyNotebook(item),
|
||||
},
|
||||
{
|
||||
label: "Download",
|
||||
iconSrc: NotebookIcon,
|
||||
onClick: () => this.container.downloadFile(item)
|
||||
}
|
||||
onClick: () => this.container.downloadFile(item),
|
||||
},
|
||||
];
|
||||
|
||||
// "Copy to ..." isn't needed if github locations are not available
|
||||
if (!this.container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
|
||||
items = items.filter(item => item.label !== "Copy to ...");
|
||||
items = items.filter((item) => item.label !== "Copy to ...");
|
||||
}
|
||||
|
||||
return items;
|
||||
@@ -733,7 +736,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
{
|
||||
label: "Refresh",
|
||||
iconSrc: RefreshIcon,
|
||||
onClick: () => this.container.refreshContentItem(item).then(() => this.triggerRender())
|
||||
onClick: () => this.container.refreshContentItem(item).then(() => this.triggerRender()),
|
||||
},
|
||||
{
|
||||
label: "Delete",
|
||||
@@ -747,34 +750,34 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
"Cancel",
|
||||
undefined
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Rename",
|
||||
iconSrc: NotebookIcon,
|
||||
onClick: () => this.container.renameNotebook(item).then(() => this.triggerRender())
|
||||
onClick: () => this.container.renameNotebook(item).then(() => this.triggerRender()),
|
||||
},
|
||||
{
|
||||
label: "New Directory",
|
||||
iconSrc: NewNotebookIcon,
|
||||
onClick: () => this.container.onCreateDirectory(item)
|
||||
onClick: () => this.container.onCreateDirectory(item),
|
||||
},
|
||||
{
|
||||
label: "New Notebook",
|
||||
iconSrc: NewNotebookIcon,
|
||||
onClick: () => this.container.onNewNotebookClicked(item)
|
||||
onClick: () => this.container.onNewNotebookClicked(item),
|
||||
},
|
||||
{
|
||||
label: "Upload File",
|
||||
iconSrc: NewNotebookIcon,
|
||||
onClick: () => this.container.onUploadToNotebookServerClicked(item)
|
||||
}
|
||||
onClick: () => this.container.onUploadToNotebookServerClicked(item),
|
||||
},
|
||||
];
|
||||
|
||||
// For GitHub paths remove "Delete", "Rename", "New Directory", "Upload File"
|
||||
if (GitHubUtils.fromContentUri(item.path)) {
|
||||
items = items.filter(
|
||||
item =>
|
||||
(item) =>
|
||||
item.label !== "Delete" &&
|
||||
item.label !== "Rename" &&
|
||||
item.label !== "New Directory" &&
|
||||
@@ -818,7 +821,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
? this.createDirectoryContextMenu(item)
|
||||
: undefined,
|
||||
data: item,
|
||||
children: this.buildChildNodes(item, onFileClick, createDirectoryContextMenu, createFileContextMenu)
|
||||
children: this.buildChildNodes(item, onFileClick, createDirectoryContextMenu, createFileContextMenu),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ describe("Resource tree for resource token", () => {
|
||||
const rootNode: TreeNode = resourceTree.buildCollectionNode();
|
||||
const props: TreeComponentProps = {
|
||||
rootNode,
|
||||
className: "dataResourceTree"
|
||||
className: "dataResourceTree",
|
||||
};
|
||||
const wrapper = shallow(<TreeComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
@@ -34,7 +34,7 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
|
||||
return {
|
||||
label: undefined,
|
||||
isExpanded: true,
|
||||
children: []
|
||||
children: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -50,12 +50,12 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
|
||||
description: "Data",
|
||||
data: {
|
||||
databaseId: collection.databaseId,
|
||||
collectionId: collection.id()
|
||||
}
|
||||
collectionId: collection.id(),
|
||||
},
|
||||
});
|
||||
},
|
||||
isSelected: () =>
|
||||
this.isDataNodeSelected(collection.databaseId, collection.id(), ViewModels.CollectionTabKind.Documents)
|
||||
this.isDataNodeSelected(collection.databaseId, collection.id(), ViewModels.CollectionTabKind.Documents),
|
||||
});
|
||||
|
||||
const collectionNode: TreeNode = {
|
||||
@@ -69,16 +69,16 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
|
||||
this.container.selectedNode(collection);
|
||||
this.container.onUpdateTabsButtons([]);
|
||||
this.container.tabsManager.refreshActiveTab(
|
||||
tab => tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
(tab) => tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId
|
||||
);
|
||||
},
|
||||
isSelected: () => this.isDataNodeSelected(collection.databaseId, collection.id())
|
||||
isSelected: () => this.isDataNodeSelected(collection.databaseId, collection.id()),
|
||||
};
|
||||
|
||||
return {
|
||||
label: undefined,
|
||||
isExpanded: true,
|
||||
children: [collectionNode]
|
||||
children: [collectionNode],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,175 +1,175 @@
|
||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||
import * as ko from "knockout";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { deleteStoredProcedure } from "../../Common/dataAccess/deleteStoredProcedure";
|
||||
import { executeStoredProcedure } from "../../Common/dataAccess/executeStoredProcedure";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import Explorer from "../Explorer";
|
||||
import StoredProcedureTab from "../Tabs/StoredProcedureTab";
|
||||
import TabsBase from "../Tabs/TabsBase";
|
||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
const sampleStoredProcedureBody: string = `// SAMPLE STORED PROCEDURE
|
||||
function sample(prefix) {
|
||||
var collection = getContext().getCollection();
|
||||
|
||||
// Query documents and take 1st item.
|
||||
var isAccepted = collection.queryDocuments(
|
||||
collection.getSelfLink(),
|
||||
'SELECT * FROM root r',
|
||||
function (err, feed, options) {
|
||||
if (err) throw err;
|
||||
|
||||
// Check the feed and if empty, set the body to 'no docs found',
|
||||
// else take 1st element from feed
|
||||
if (!feed || !feed.length) {
|
||||
var response = getContext().getResponse();
|
||||
response.setBody('no docs found');
|
||||
}
|
||||
else {
|
||||
var response = getContext().getResponse();
|
||||
var body = { prefix: prefix, feed: feed[0] };
|
||||
response.setBody(JSON.stringify(body));
|
||||
}
|
||||
});
|
||||
|
||||
if (!isAccepted) throw new Error('The query was not accepted by the server.');
|
||||
}`;
|
||||
|
||||
export default class StoredProcedure {
|
||||
public nodeKind: string;
|
||||
public container: Explorer;
|
||||
public collection: ViewModels.Collection;
|
||||
public self: string;
|
||||
public rid: string;
|
||||
public id: ko.Observable<string>;
|
||||
public body: ko.Observable<string>;
|
||||
public isExecuteEnabled: boolean;
|
||||
|
||||
constructor(container: Explorer, collection: ViewModels.Collection, data: StoredProcedureDefinition & Resource) {
|
||||
this.nodeKind = "StoredProcedure";
|
||||
this.container = container;
|
||||
this.collection = collection;
|
||||
this.self = data._self;
|
||||
this.rid = data._rid;
|
||||
this.id = ko.observable(data.id);
|
||||
this.body = ko.observable(data.body as string);
|
||||
this.isExecuteEnabled = this.container.isFeatureEnabled(Constants.Features.executeSproc);
|
||||
}
|
||||
|
||||
public static create(source: ViewModels.Collection, event: MouseEvent) {
|
||||
const id = source.container.tabsManager.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`,
|
||||
isActive: ko.observable(false),
|
||||
onUpdateTabsButtons: source.container.onUpdateTabsButtons
|
||||
});
|
||||
|
||||
source.container.tabsManager.activateNewTab(storedProcedureTab);
|
||||
}
|
||||
|
||||
public select() {
|
||||
this.container.selectedNode(this);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Stored procedure node",
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
});
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
if (storedProcedureTab) {
|
||||
this.container.tabsManager.activateTab(storedProcedureTab);
|
||||
} else {
|
||||
const storedProcedureData = <StoredProcedureDefinition>{
|
||||
_rid: this.rid,
|
||||
_self: this.self,
|
||||
id: this.id(),
|
||||
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()}`,
|
||||
isActive: ko.observable(false),
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(storedProcedureTab);
|
||||
}
|
||||
};
|
||||
|
||||
public delete() {
|
||||
if (!window.confirm("Are you sure you want to delete the stored procedure?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
deleteStoredProcedure(this.collection.databaseId, this.collection.id(), this.id()).then(
|
||||
() => {
|
||||
this.container.tabsManager.removeTabByComparator((tab: TabsBase) => tab.node && tab.node.rid === this.rid);
|
||||
this.collection.children.remove(this);
|
||||
},
|
||||
reason => {}
|
||||
);
|
||||
}
|
||||
|
||||
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];
|
||||
sprocTab.isExecuting(true);
|
||||
this.container &&
|
||||
executeStoredProcedure(this.collection, this, partitionKeyValue, params)
|
||||
.then(
|
||||
(result: any) => {
|
||||
sprocTab.onExecuteSprocsResult(result, result.scriptLogs);
|
||||
},
|
||||
(error: any) => {
|
||||
sprocTab.onExecuteSprocsError(getErrorMessage(error));
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
sprocTab.isExecuting(false);
|
||||
this.onFocusAfterExecute();
|
||||
});
|
||||
}
|
||||
|
||||
public onFocusAfterExecute(): void {
|
||||
const focusElement = document.getElementById("execute-storedproc-toggles");
|
||||
focusElement && focusElement.focus();
|
||||
}
|
||||
}
|
||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||
import * as ko from "knockout";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { deleteStoredProcedure } from "../../Common/dataAccess/deleteStoredProcedure";
|
||||
import { executeStoredProcedure } from "../../Common/dataAccess/executeStoredProcedure";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import Explorer from "../Explorer";
|
||||
import StoredProcedureTab from "../Tabs/StoredProcedureTab";
|
||||
import TabsBase from "../Tabs/TabsBase";
|
||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
|
||||
const sampleStoredProcedureBody: string = `// SAMPLE STORED PROCEDURE
|
||||
function sample(prefix) {
|
||||
var collection = getContext().getCollection();
|
||||
|
||||
// Query documents and take 1st item.
|
||||
var isAccepted = collection.queryDocuments(
|
||||
collection.getSelfLink(),
|
||||
'SELECT * FROM root r',
|
||||
function (err, feed, options) {
|
||||
if (err) throw err;
|
||||
|
||||
// Check the feed and if empty, set the body to 'no docs found',
|
||||
// else take 1st element from feed
|
||||
if (!feed || !feed.length) {
|
||||
var response = getContext().getResponse();
|
||||
response.setBody('no docs found');
|
||||
}
|
||||
else {
|
||||
var response = getContext().getResponse();
|
||||
var body = { prefix: prefix, feed: feed[0] };
|
||||
response.setBody(JSON.stringify(body));
|
||||
}
|
||||
});
|
||||
|
||||
if (!isAccepted) throw new Error('The query was not accepted by the server.');
|
||||
}`;
|
||||
|
||||
export default class StoredProcedure {
|
||||
public nodeKind: string;
|
||||
public container: Explorer;
|
||||
public collection: ViewModels.Collection;
|
||||
public self: string;
|
||||
public rid: string;
|
||||
public id: ko.Observable<string>;
|
||||
public body: ko.Observable<string>;
|
||||
public isExecuteEnabled: boolean;
|
||||
|
||||
constructor(container: Explorer, collection: ViewModels.Collection, data: StoredProcedureDefinition & Resource) {
|
||||
this.nodeKind = "StoredProcedure";
|
||||
this.container = container;
|
||||
this.collection = collection;
|
||||
this.self = data._self;
|
||||
this.rid = data._rid;
|
||||
this.id = ko.observable(data.id);
|
||||
this.body = ko.observable(data.body as string);
|
||||
this.isExecuteEnabled = this.container.isFeatureEnabled(Constants.Features.executeSproc);
|
||||
}
|
||||
|
||||
public static create(source: ViewModels.Collection, event: MouseEvent) {
|
||||
const id = source.container.tabsManager.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`,
|
||||
isActive: ko.observable(false),
|
||||
onUpdateTabsButtons: source.container.onUpdateTabsButtons,
|
||||
});
|
||||
|
||||
source.container.tabsManager.activateNewTab(storedProcedureTab);
|
||||
}
|
||||
|
||||
public select() {
|
||||
this.container.selectedNode(this);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Stored procedure node",
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
if (storedProcedureTab) {
|
||||
this.container.tabsManager.activateTab(storedProcedureTab);
|
||||
} else {
|
||||
const storedProcedureData = <StoredProcedureDefinition>{
|
||||
_rid: this.rid,
|
||||
_self: this.self,
|
||||
id: this.id(),
|
||||
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()}`,
|
||||
isActive: ko.observable(false),
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(storedProcedureTab);
|
||||
}
|
||||
};
|
||||
|
||||
public delete() {
|
||||
if (!window.confirm("Are you sure you want to delete the stored procedure?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
deleteStoredProcedure(this.collection.databaseId, this.collection.id(), this.id()).then(
|
||||
() => {
|
||||
this.container.tabsManager.removeTabByComparator((tab: TabsBase) => tab.node && tab.node.rid === this.rid);
|
||||
this.collection.children.remove(this);
|
||||
},
|
||||
(reason) => {}
|
||||
);
|
||||
}
|
||||
|
||||
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];
|
||||
sprocTab.isExecuting(true);
|
||||
this.container &&
|
||||
executeStoredProcedure(this.collection, this, partitionKeyValue, params)
|
||||
.then(
|
||||
(result: any) => {
|
||||
sprocTab.onExecuteSprocsResult(result, result.scriptLogs);
|
||||
},
|
||||
(error: any) => {
|
||||
sprocTab.onExecuteSprocsError(getErrorMessage(error));
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
sprocTab.isExecuting(false);
|
||||
this.onFocusAfterExecute();
|
||||
});
|
||||
}
|
||||
|
||||
public onFocusAfterExecute(): void {
|
||||
const focusElement = document.getElementById("execute-storedproc-toggles");
|
||||
focusElement && focusElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,123 +1,123 @@
|
||||
import { StoredProcedureDefinition } from "@azure/cosmos";
|
||||
import * as ko from "knockout";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { deleteTrigger } from "../../Common/dataAccess/deleteTrigger";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import Explorer from "../Explorer";
|
||||
import TriggerTab from "../Tabs/TriggerTab";
|
||||
|
||||
export default class Trigger {
|
||||
public nodeKind: string;
|
||||
public container: Explorer;
|
||||
public collection: ViewModels.Collection;
|
||||
public self: string;
|
||||
public rid: string;
|
||||
public id: ko.Observable<string>;
|
||||
public body: ko.Observable<string>;
|
||||
public triggerType: ko.Observable<string>;
|
||||
public triggerOperation: ko.Observable<string>;
|
||||
|
||||
constructor(container: Explorer, collection: ViewModels.Collection, data: any) {
|
||||
this.nodeKind = "Trigger";
|
||||
this.container = container;
|
||||
this.collection = collection;
|
||||
this.self = data._self;
|
||||
this.rid = data._rid;
|
||||
this.id = ko.observable(data.id);
|
||||
this.body = ko.observable(data.body);
|
||||
this.triggerOperation = ko.observable(data.triggerOperation);
|
||||
this.triggerType = ko.observable(data.triggerType);
|
||||
}
|
||||
|
||||
public select() {
|
||||
this.container.selectedNode(this);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Trigger node",
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
});
|
||||
}
|
||||
|
||||
public static create(source: ViewModels.Collection, event: MouseEvent) {
|
||||
const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Triggers).length + 1;
|
||||
const trigger = <StoredProcedureDefinition>{
|
||||
id: "",
|
||||
body: "function trigger(){}",
|
||||
triggerOperation: "All",
|
||||
triggerType: "Pre"
|
||||
};
|
||||
|
||||
const triggerTab: TriggerTab = new TriggerTab({
|
||||
resource: trigger,
|
||||
isNew: true,
|
||||
tabKind: ViewModels.CollectionTabKind.Triggers,
|
||||
title: `New Trigger ${id}`,
|
||||
tabPath: "",
|
||||
collection: source,
|
||||
node: source,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/trigger`,
|
||||
isActive: ko.observable(false),
|
||||
onUpdateTabsButtons: source.container.onUpdateTabsButtons
|
||||
});
|
||||
|
||||
source.container.tabsManager.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[];
|
||||
let triggerTab: TriggerTab = triggerTabs && triggerTabs[0];
|
||||
|
||||
if (triggerTab) {
|
||||
this.container.tabsManager.activateTab(triggerTab);
|
||||
} else {
|
||||
const triggerData = <StoredProcedureDefinition>{
|
||||
_rid: this.rid,
|
||||
_self: this.self,
|
||||
id: this.id(),
|
||||
body: this.body(),
|
||||
triggerOperation: this.triggerOperation(),
|
||||
triggerType: this.triggerType()
|
||||
};
|
||||
|
||||
triggerTab = new TriggerTab({
|
||||
resource: triggerData,
|
||||
isNew: false,
|
||||
tabKind: ViewModels.CollectionTabKind.Triggers,
|
||||
title: triggerData.id,
|
||||
tabPath: "",
|
||||
collection: this.collection,
|
||||
node: this,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(
|
||||
this.collection.databaseId,
|
||||
this.collection.id()
|
||||
)}/triggers/${this.id()}`,
|
||||
isActive: ko.observable(false),
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(triggerTab);
|
||||
}
|
||||
};
|
||||
|
||||
public delete() {
|
||||
if (!window.confirm("Are you sure you want to delete the trigger?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
deleteTrigger(this.collection.databaseId, this.collection.id(), this.id()).then(
|
||||
() => {
|
||||
this.container.tabsManager.removeTabByComparator(tab => tab.node && tab.node.rid === this.rid);
|
||||
this.collection.children.remove(this);
|
||||
},
|
||||
reason => {}
|
||||
);
|
||||
}
|
||||
}
|
||||
import { StoredProcedureDefinition } from "@azure/cosmos";
|
||||
import * as ko from "knockout";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { deleteTrigger } from "../../Common/dataAccess/deleteTrigger";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import Explorer from "../Explorer";
|
||||
import TriggerTab from "../Tabs/TriggerTab";
|
||||
|
||||
export default class Trigger {
|
||||
public nodeKind: string;
|
||||
public container: Explorer;
|
||||
public collection: ViewModels.Collection;
|
||||
public self: string;
|
||||
public rid: string;
|
||||
public id: ko.Observable<string>;
|
||||
public body: ko.Observable<string>;
|
||||
public triggerType: ko.Observable<string>;
|
||||
public triggerOperation: ko.Observable<string>;
|
||||
|
||||
constructor(container: Explorer, collection: ViewModels.Collection, data: any) {
|
||||
this.nodeKind = "Trigger";
|
||||
this.container = container;
|
||||
this.collection = collection;
|
||||
this.self = data._self;
|
||||
this.rid = data._rid;
|
||||
this.id = ko.observable(data.id);
|
||||
this.body = ko.observable(data.body);
|
||||
this.triggerOperation = ko.observable(data.triggerOperation);
|
||||
this.triggerType = ko.observable(data.triggerType);
|
||||
}
|
||||
|
||||
public select() {
|
||||
this.container.selectedNode(this);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "Trigger node",
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
}
|
||||
|
||||
public static create(source: ViewModels.Collection, event: MouseEvent) {
|
||||
const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Triggers).length + 1;
|
||||
const trigger = <StoredProcedureDefinition>{
|
||||
id: "",
|
||||
body: "function trigger(){}",
|
||||
triggerOperation: "All",
|
||||
triggerType: "Pre",
|
||||
};
|
||||
|
||||
const triggerTab: TriggerTab = new TriggerTab({
|
||||
resource: trigger,
|
||||
isNew: true,
|
||||
tabKind: ViewModels.CollectionTabKind.Triggers,
|
||||
title: `New Trigger ${id}`,
|
||||
tabPath: "",
|
||||
collection: source,
|
||||
node: source,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/trigger`,
|
||||
isActive: ko.observable(false),
|
||||
onUpdateTabsButtons: source.container.onUpdateTabsButtons,
|
||||
});
|
||||
|
||||
source.container.tabsManager.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[];
|
||||
let triggerTab: TriggerTab = triggerTabs && triggerTabs[0];
|
||||
|
||||
if (triggerTab) {
|
||||
this.container.tabsManager.activateTab(triggerTab);
|
||||
} else {
|
||||
const triggerData = <StoredProcedureDefinition>{
|
||||
_rid: this.rid,
|
||||
_self: this.self,
|
||||
id: this.id(),
|
||||
body: this.body(),
|
||||
triggerOperation: this.triggerOperation(),
|
||||
triggerType: this.triggerType(),
|
||||
};
|
||||
|
||||
triggerTab = new TriggerTab({
|
||||
resource: triggerData,
|
||||
isNew: false,
|
||||
tabKind: ViewModels.CollectionTabKind.Triggers,
|
||||
title: triggerData.id,
|
||||
tabPath: "",
|
||||
collection: this.collection,
|
||||
node: this,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(
|
||||
this.collection.databaseId,
|
||||
this.collection.id()
|
||||
)}/triggers/${this.id()}`,
|
||||
isActive: ko.observable(false),
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(triggerTab);
|
||||
}
|
||||
};
|
||||
|
||||
public delete() {
|
||||
if (!window.confirm("Are you sure you want to delete the trigger?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
deleteTrigger(this.collection.databaseId, this.collection.id(), this.id()).then(
|
||||
() => {
|
||||
this.container.tabsManager.removeTabByComparator((tab) => tab.node && tab.node.rid === this.rid);
|
||||
this.collection.children.remove(this);
|
||||
},
|
||||
(reason) => {}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,116 +1,116 @@
|
||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||
import * as ko from "knockout";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { deleteUserDefinedFunction } from "../../Common/dataAccess/deleteUserDefinedFunction";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import Explorer from "../Explorer";
|
||||
import UserDefinedFunctionTab from "../Tabs/UserDefinedFunctionTab";
|
||||
|
||||
export default class UserDefinedFunction {
|
||||
public nodeKind: string;
|
||||
public container: Explorer;
|
||||
public collection: ViewModels.Collection;
|
||||
public self: string;
|
||||
public rid: string;
|
||||
public id: ko.Observable<string>;
|
||||
public body: ko.Observable<string>;
|
||||
|
||||
constructor(container: Explorer, collection: ViewModels.Collection, data: UserDefinedFunctionDefinition & Resource) {
|
||||
this.nodeKind = "UserDefinedFunction";
|
||||
this.container = container;
|
||||
|
||||
this.collection = collection;
|
||||
this.self = data._self;
|
||||
this.rid = data._rid;
|
||||
this.id = ko.observable(data.id);
|
||||
this.body = ko.observable(data.body as string);
|
||||
}
|
||||
|
||||
public static create(source: ViewModels.Collection, event: MouseEvent) {
|
||||
const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.UserDefinedFunctions).length + 1;
|
||||
const userDefinedFunction = {
|
||||
id: "",
|
||||
body: "function userDefinedFunction(){}"
|
||||
};
|
||||
|
||||
const userDefinedFunctionTab: UserDefinedFunctionTab = new UserDefinedFunctionTab({
|
||||
resource: userDefinedFunction,
|
||||
isNew: true,
|
||||
tabKind: ViewModels.CollectionTabKind.UserDefinedFunctions,
|
||||
title: `New UDF ${id}`,
|
||||
tabPath: "",
|
||||
collection: source,
|
||||
node: source,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/udf`,
|
||||
isActive: ko.observable(false),
|
||||
onUpdateTabsButtons: source.container.onUpdateTabsButtons
|
||||
});
|
||||
|
||||
source.container.tabsManager.activateNewTab(userDefinedFunctionTab);
|
||||
}
|
||||
|
||||
public open = () => {
|
||||
this.select();
|
||||
|
||||
const userDefinedFunctionTabs: UserDefinedFunctionTab[] = this.container.tabsManager.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);
|
||||
} else {
|
||||
const userDefinedFunctionData = {
|
||||
_rid: this.rid,
|
||||
_self: this.self,
|
||||
id: this.id(),
|
||||
body: this.body()
|
||||
};
|
||||
|
||||
userDefinedFunctionTab = new UserDefinedFunctionTab({
|
||||
resource: userDefinedFunctionData,
|
||||
isNew: false,
|
||||
tabKind: ViewModels.CollectionTabKind.UserDefinedFunctions,
|
||||
title: userDefinedFunctionData.id,
|
||||
tabPath: "",
|
||||
collection: this.collection,
|
||||
node: this,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(
|
||||
this.collection.databaseId,
|
||||
this.collection.id()
|
||||
)}/udfs/${this.id()}`,
|
||||
isActive: ko.observable(false),
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(userDefinedFunctionTab);
|
||||
}
|
||||
};
|
||||
|
||||
public select() {
|
||||
this.container.selectedNode(this);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "UDF item node",
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree
|
||||
});
|
||||
}
|
||||
|
||||
public delete() {
|
||||
if (!window.confirm("Are you sure you want to delete the user defined function?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
deleteUserDefinedFunction(this.collection.databaseId, this.collection.id(), this.id()).then(
|
||||
() => {
|
||||
this.container.tabsManager.removeTabByComparator(tab => tab.node && tab.node.rid === this.rid);
|
||||
this.collection.children.remove(this);
|
||||
},
|
||||
reason => {}
|
||||
);
|
||||
}
|
||||
}
|
||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||
import * as ko from "knockout";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { deleteUserDefinedFunction } from "../../Common/dataAccess/deleteUserDefinedFunction";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import Explorer from "../Explorer";
|
||||
import UserDefinedFunctionTab from "../Tabs/UserDefinedFunctionTab";
|
||||
|
||||
export default class UserDefinedFunction {
|
||||
public nodeKind: string;
|
||||
public container: Explorer;
|
||||
public collection: ViewModels.Collection;
|
||||
public self: string;
|
||||
public rid: string;
|
||||
public id: ko.Observable<string>;
|
||||
public body: ko.Observable<string>;
|
||||
|
||||
constructor(container: Explorer, collection: ViewModels.Collection, data: UserDefinedFunctionDefinition & Resource) {
|
||||
this.nodeKind = "UserDefinedFunction";
|
||||
this.container = container;
|
||||
|
||||
this.collection = collection;
|
||||
this.self = data._self;
|
||||
this.rid = data._rid;
|
||||
this.id = ko.observable(data.id);
|
||||
this.body = ko.observable(data.body as string);
|
||||
}
|
||||
|
||||
public static create(source: ViewModels.Collection, event: MouseEvent) {
|
||||
const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.UserDefinedFunctions).length + 1;
|
||||
const userDefinedFunction = {
|
||||
id: "",
|
||||
body: "function userDefinedFunction(){}",
|
||||
};
|
||||
|
||||
const userDefinedFunctionTab: UserDefinedFunctionTab = new UserDefinedFunctionTab({
|
||||
resource: userDefinedFunction,
|
||||
isNew: true,
|
||||
tabKind: ViewModels.CollectionTabKind.UserDefinedFunctions,
|
||||
title: `New UDF ${id}`,
|
||||
tabPath: "",
|
||||
collection: source,
|
||||
node: source,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/udf`,
|
||||
isActive: ko.observable(false),
|
||||
onUpdateTabsButtons: source.container.onUpdateTabsButtons,
|
||||
});
|
||||
|
||||
source.container.tabsManager.activateNewTab(userDefinedFunctionTab);
|
||||
}
|
||||
|
||||
public open = () => {
|
||||
this.select();
|
||||
|
||||
const userDefinedFunctionTabs: UserDefinedFunctionTab[] = this.container.tabsManager.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);
|
||||
} else {
|
||||
const userDefinedFunctionData = {
|
||||
_rid: this.rid,
|
||||
_self: this.self,
|
||||
id: this.id(),
|
||||
body: this.body(),
|
||||
};
|
||||
|
||||
userDefinedFunctionTab = new UserDefinedFunctionTab({
|
||||
resource: userDefinedFunctionData,
|
||||
isNew: false,
|
||||
tabKind: ViewModels.CollectionTabKind.UserDefinedFunctions,
|
||||
title: userDefinedFunctionData.id,
|
||||
tabPath: "",
|
||||
collection: this.collection,
|
||||
node: this,
|
||||
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(
|
||||
this.collection.databaseId,
|
||||
this.collection.id()
|
||||
)}/udfs/${this.id()}`,
|
||||
isActive: ko.observable(false),
|
||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||
});
|
||||
|
||||
this.container.tabsManager.activateNewTab(userDefinedFunctionTab);
|
||||
}
|
||||
};
|
||||
|
||||
public select() {
|
||||
this.container.selectedNode(this);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
description: "UDF item node",
|
||||
databaseAccountName: this.container.databaseAccount().name,
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||
});
|
||||
}
|
||||
|
||||
public delete() {
|
||||
if (!window.confirm("Are you sure you want to delete the user defined function?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
deleteUserDefinedFunction(this.collection.databaseId, this.collection.id(), this.id()).then(
|
||||
() => {
|
||||
this.container.tabsManager.removeTabByComparator((tab) => tab.node && tab.node.rid === this.rid);
|
||||
this.collection.children.remove(this);
|
||||
},
|
||||
(reason) => {}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user